Fix device linking issues on newer devices.

fork-5.53.8
Cody Henthorne 2022-05-19 16:26:03 -04:00 zatwierdzone przez Alex Hart
rodzic cc31417c97
commit d260c48393
47 zmienionych plików z 1387 dodań i 236 usunięć

Wyświetl plik

@ -455,6 +455,7 @@ dependencies {
implementation project(':image-editor')
implementation project(':donations')
implementation project(':contacts')
implementation project(':qr')
implementation libs.libsignal.android
implementation libs.google.protobuf.javalite

Wyświetl plik

@ -25,11 +25,11 @@ import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.ecc.Curve;
import org.signal.libsignal.protocol.ecc.ECPublicKey;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.signal.qr.kitkat.ScanListener;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.qr.ScanListener;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;

Wyświetl plik

@ -15,26 +15,25 @@ import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.components.camera.CameraView;
import org.thoughtcrime.securesms.qr.ScanListener;
import org.thoughtcrime.securesms.qr.ScanningThread;
import org.signal.qr.QrScannerView;
import org.signal.qr.kitkat.ScanListener;
import org.thoughtcrime.securesms.util.LifecycleDisposable;
import org.thoughtcrime.securesms.util.ViewUtil;
public class DeviceAddFragment extends LoggingFragment {
private ViewGroup container;
private LinearLayout overlay;
private ImageView devicesImage;
private CameraView scannerView;
private ScanningThread scanningThread;
private ScanListener scanListener;
private final LifecycleDisposable lifecycleDisposable = new LifecycleDisposable();
private LinearLayout overlay;
private ImageView devicesImage;
private ScanListener scanListener;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.device_add_fragment);
this.overlay = this.container.findViewById(R.id.overlay);
this.scannerView = this.container.findViewById(R.id.scanner);
this.devicesImage = this.container.findViewById(R.id.devices);
ViewGroup container = ViewUtil.inflate(inflater, viewGroup, R.layout.device_add_fragment);
this.overlay = container.findViewById(R.id.overlay);
QrScannerView scannerView = container.findViewById(R.id.scanner);
this.devicesImage = container.findViewById(R.id.devices);
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
this.overlay.setOrientation(LinearLayout.HORIZONTAL);
@ -43,7 +42,7 @@ public class DeviceAddFragment extends LoggingFragment {
}
if (Build.VERSION.SDK_INT >= 21) {
this.container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@TargetApi(21)
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
@ -59,52 +58,34 @@ public class DeviceAddFragment extends LoggingFragment {
});
}
return this.container;
}
scannerView.start(getViewLifecycleOwner());
@Override
public void onResume() {
super.onResume();
this.scanningThread = new ScanningThread();
this.scanningThread.setScanListener(scanListener);
this.scannerView.onResume();
this.scannerView.setPreviewCallback(scanningThread);
this.scanningThread.start();
}
lifecycleDisposable.bindTo(getViewLifecycleOwner());
lifecycleDisposable.add(scannerView.getQrData().subscribe(qrData -> {
if (scanListener != null) {
scanListener.onQrDataFound(qrData);
}
}));
@Override
public void onPause() {
super.onPause();
this.scannerView.onPause();
this.scanningThread.stopScanning();
return container;
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfiguration) {
super.onConfigurationChanged(newConfiguration);
this.scannerView.onPause();
if (newConfiguration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
overlay.setOrientation(LinearLayout.HORIZONTAL);
} else {
overlay.setOrientation(LinearLayout.VERTICAL);
}
this.scannerView.onResume();
this.scannerView.setPreviewCallback(scanningThread);
}
public ImageView getDevicesImage() {
return devicesImage;
}
public void setScanListener(ScanListener scanListener) {
this.scanListener = scanListener;
if (this.scanningThread != null) {
this.scanningThread.setScanListener(scanListener);
}
}
}

Wyświetl plik

@ -38,6 +38,7 @@ import androidx.annotation.Nullable;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log;
import org.signal.qr.kitkat.QrCameraView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@ -228,7 +229,7 @@ public class CameraView extends ViewGroup {
listeners.add(listener);
}
public void setPreviewCallback(final @NonNull PreviewCallback previewCallback) {
public void setPreviewCallback(final @NonNull QrCameraView.PreviewCallback previewCallback) {
enqueueTask(new PostInitializationTask<Void>() {
@Override
protected void onPostMain(Void avoid) {
@ -243,7 +244,7 @@ public class CameraView extends ViewGroup {
final int rotation = getCameraPictureOrientation();
final Size previewSize = camera.getParameters().getPreviewSize();
if (data != null) {
previewCallback.onPreviewFrame(new PreviewFrame(data, previewSize.width, previewSize.height, rotation));
previewCallback.onPreviewFrame(new QrCameraView.PreviewFrame(data, previewSize.width, previewSize.height, rotation));
}
}
});
@ -568,40 +569,6 @@ public class CameraView extends ViewGroup {
void onCameraStop();
}
public interface PreviewCallback {
void onPreviewFrame(@NonNull PreviewFrame frame);
}
public static class PreviewFrame {
private final @NonNull byte[] data;
private final int width;
private final int height;
private final int orientation;
private PreviewFrame(@NonNull byte[] data, int width, int height, int orientation) {
this.data = data;
this.width = width;
this.height = height;
this.orientation = orientation;
}
public @NonNull byte[] getData() {
return data;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public int getOrientation() {
return orientation;
}
}
private enum State {
PAUSED, RESUMED, ACTIVE
}

Wyświetl plik

@ -13,11 +13,11 @@ import androidx.navigation.Navigation;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log;
import org.signal.qr.kitkat.ScanningThread;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.camera.CameraView;
import org.thoughtcrime.securesms.payments.MobileCoinPublicAddress;
import org.thoughtcrime.securesms.qr.ScanningThread;
import org.thoughtcrime.securesms.util.navigation.SafeNavigation;
public final class PaymentsTransferQrScanFragment extends LoggingFragment {

Wyświetl plik

@ -1,126 +0,0 @@
package org.thoughtcrime.securesms.qr;
import android.content.res.Configuration;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.ChecksumException;
import com.google.zxing.DecodeHintType;
import com.google.zxing.FormatException;
import com.google.zxing.NotFoundException;
import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.QRCodeReader;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.components.camera.CameraView;
import org.thoughtcrime.securesms.components.camera.CameraView.PreviewFrame;
import org.thoughtcrime.securesms.util.Util;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
public class ScanningThread extends Thread implements CameraView.PreviewCallback {
private static final String TAG = Log.tag(ScanningThread.class);
private final QRCodeReader reader = new QRCodeReader();
private final AtomicReference<ScanListener> scanListener = new AtomicReference<>();
private final Map<DecodeHintType, String> hints = new HashMap<>();
private boolean scanning = true;
private PreviewFrame previewFrame;
public void setCharacterSet(String characterSet) {
hints.put(DecodeHintType.CHARACTER_SET, characterSet);
}
public void setScanListener(ScanListener scanListener) {
this.scanListener.set(scanListener);
}
@Override
public void onPreviewFrame(@NonNull PreviewFrame previewFrame) {
try {
synchronized (this) {
this.previewFrame = previewFrame;
this.notify();
}
} catch (RuntimeException e) {
Log.w(TAG, e);
}
}
@Override
public void run() {
while (true) {
PreviewFrame ourFrame;
synchronized (this) {
while (scanning && previewFrame == null) {
Util.wait(this, 0);
}
if (!scanning) return;
else ourFrame = previewFrame;
previewFrame = null;
}
String data = getScannedData(ourFrame.getData(), ourFrame.getWidth(), ourFrame.getHeight(), ourFrame.getOrientation());
ScanListener scanListener = this.scanListener.get();
if (data != null && scanListener != null) {
scanListener.onQrDataFound(data);
return;
}
}
}
public void stopScanning() {
synchronized (this) {
scanning = false;
notify();
}
}
private @Nullable String getScannedData(byte[] data, int width, int height, int orientation) {
try {
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
byte[] rotatedData = new byte[data.length];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
rotatedData[x * height + height - y - 1] = data[x + y * width];
}
}
int tmp = width;
width = height;
height = tmp;
data = rotatedData;
}
PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(data, width, height,
0, 0, width, height,
false);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
Result result = reader.decode(bitmap, hints);
if (result != null) return result.getText();
} catch (NullPointerException | ChecksumException | FormatException e) {
Log.w(TAG, e);
} catch (NotFoundException e) {
// Thanks ZXing...
}
return null;
}
}

Wyświetl plik

@ -6,11 +6,11 @@ import android.view.View
import android.widget.Toast
import androidx.fragment.app.Fragment
import org.signal.core.util.ThreadUtil
import org.signal.qr.kitkat.ScanListener
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.qr.ScanListener
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.ServiceUtil

Wyświetl plik

@ -9,11 +9,11 @@ import android.view.ViewGroup
import android.widget.ImageView
import androidx.core.view.OneShotPreDrawListener
import androidx.fragment.app.Fragment
import org.signal.qr.kitkat.ScanListener
import org.signal.qr.kitkat.ScanningThread
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.ShapeScrim
import org.thoughtcrime.securesms.components.camera.CameraView
import org.thoughtcrime.securesms.qr.ScanListener
import org.thoughtcrime.securesms.qr.ScanningThread
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.fragments.requireListener

Wyświetl plik

@ -1,51 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
<org.thoughtcrime.securesms.components.camera.CameraView
android:id="@+id/scanner"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:camera="0"/>
<org.signal.qr.QrScannerView
android:id="@+id/scanner"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout android:id="@+id/overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:weightSum="2">
<LinearLayout
android:id="@+id/overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:weightSum="2">
<org.thoughtcrime.securesms.components.ShapeScrim
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:layout_height="match_parent"
android:layout_weight="1" />
<LinearLayout android:orientation="vertical"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:background="?android:windowBackground"
android:gravity="center">
android:gravity="center"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/devices"
android:src="@drawable/ic_devices_white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:src="@drawable/ic_devices_white"
android:tint="@color/core_grey_25"
android:transitionName="devices"
android:layout_marginBottom="16dp"/>
android:transitionName="devices" />
<TextView android:text="@string/device_add_fragment__scan_the_qr_code_displayed_on_the_device_to_link"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:textColorSecondary"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/device_add_fragment__scan_the_qr_code_displayed_on_the_device_to_link"
android:textColor="?android:textColorSecondary" />
</LinearLayout>

Wyświetl plik

@ -0,0 +1,14 @@
apply from: "$rootProject.projectDir/signalModuleApp.gradle"
android {
defaultConfig {
applicationId "org.signal.qrtest"
}
}
dependencies {
implementation project(':qr')
implementation libs.rxjava3.rxjava
implementation libs.rxjava3.rxandroid
implementation libs.rxjava3.rxkotlin
}

Wyświetl plik

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.signal.qrtest">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.App">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 22 KiB

Wyświetl plik

@ -0,0 +1,28 @@
package org.signal.qrtest
import android.os.Build
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.kotlin.subscribeBy
import org.signal.qr.QrScannerView
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (Build.VERSION.SDK_INT >= 23) {
requestPermissions(arrayOf(android.Manifest.permission.CAMERA), 1)
}
val scanner = findViewById<QrScannerView>(R.id.scanner)
scanner.start(this)
scanner.qrData
.distinctUntilChanged()
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy { Toast.makeText(this, it, Toast.LENGTH_SHORT).show() }
}
}

Wyświetl plik

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

Wyświetl plik

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108"
android:tint="#000000">
<group android:scaleX="2.61"
android:scaleY="2.61"
android:translateX="22.68"
android:translateY="22.68">
<path
android:fillColor="@android:color/white"
android:pathData="M5,16c0,3.87 3.13,7 7,7s7,-3.13 7,-7v-4L5,12v4zM16.12,4.37l2.1,-2.1 -0.82,-0.83 -2.3,2.31C14.16,3.28 13.12,3 12,3s-2.16,0.28 -3.09,0.75L6.6,1.44l-0.82,0.83 2.1,2.1C6.14,5.64 5,7.68 5,10v1h14v-1c0,-2.32 -1.14,-4.36 -2.88,-5.63zM9,9c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM15,9c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z"/>
</group>
</vector>

Wyświetl plik

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_horizontal"
android:orientation="vertical">
<org.signal.qr.QrScannerView
android:id="@+id/scanner"
android:layout_width="240dp"
android:layout_height="240dp" />
</LinearLayout>
</FrameLayout>

Wyświetl plik

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Wyświetl plik

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 2.3 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 4.3 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 1.8 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 2.6 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 3.1 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 5.9 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 5.5 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 9.9 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 7.4 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 14 KiB

Wyświetl plik

@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.ContactsTest" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

Wyświetl plik

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

Wyświetl plik

@ -0,0 +1,3 @@
<resources>
<string name="app_name">QR</string>
</resources>

Wyświetl plik

@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.App" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">#2c6bed</item>
<item name="colorPrimaryVariant">#1851b4</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

Wyświetl plik

@ -0,0 +1,18 @@
apply from: "$rootProject.projectDir/signalModule.gradle"
dependencies {
implementation libs.androidx.camera.core
implementation libs.androidx.camera.camera2
implementation libs.androidx.camera.lifecycle
implementation libs.androidx.camera.view
implementation libs.androidx.lifecycle.common.java8
implementation libs.google.zxing.android.integration
implementation libs.google.zxing.core
implementation libs.rxjava3.rxjava
// Included to force proper versions for verification and match main app
implementation 'androidx.lifecycle:lifecycle-livedata:2.3.1'
implementation 'com.google.guava:guava:30.0-android'
}

Wyświetl plik

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.signal.qr">
<uses-sdk tools:overrideLibrary="androidx.camera.core,androidx.camera.camera2,androidx.camera.lifecycle,androidx.camera.view" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-permission android:name="android.permission.CAMERA" />
</manifest>

Wyświetl plik

@ -0,0 +1,50 @@
package org.signal.qr
import com.google.zxing.BinaryBitmap
import com.google.zxing.ChecksumException
import com.google.zxing.DecodeHintType
import com.google.zxing.FormatException
import com.google.zxing.NotFoundException
import com.google.zxing.PlanarYUVLuminanceSource
import com.google.zxing.Result
import com.google.zxing.common.HybridBinarizer
import com.google.zxing.qrcode.QRCodeReader
import org.signal.core.util.logging.Log
/**
* Wraps [QRCodeReader] for use from API19 or API21+.
*/
class QrProcessor {
private val reader = QRCodeReader()
fun getScannedData(
data: ByteArray,
width: Int,
height: Int
): String? {
try {
val source = PlanarYUVLuminanceSource(data, width, height, 0, 0, width, height, false)
val bitmap = BinaryBitmap(HybridBinarizer(source))
val result: Result? = reader.decode(bitmap, emptyMap<DecodeHintType, String>())
if (result != null) {
return result.text
}
} catch (e: NullPointerException) {
Log.w(TAG, e)
} catch (e: ChecksumException) {
Log.w(TAG, e)
} catch (e: FormatException) {
Log.w(TAG, e)
} catch (e: NotFoundException) {
// Thanks ZXing...
}
return null
}
companion object {
private val TAG = Log.tag(QrProcessor::class.java)
}
}

Wyświetl plik

@ -0,0 +1,50 @@
package org.signal.qr
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.widget.FrameLayout
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.subjects.PublishSubject
/**
* View for starting up a camera and scanning a QR-Code. Safe to use on an API version and
* will delegate to legacy camera APIs or CameraX APIs when appropriate.
*
* QR-code data is emitted via [qrData] observable.
*/
class QrScannerView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr), ScannerView {
private val scannerView: ScannerView
private val qrDataPublish: PublishSubject<String> = PublishSubject.create()
val qrData: Observable<String> = qrDataPublish
init {
val scannerView: FrameLayout = if (Build.VERSION.SDK_INT >= 21) {
ScannerView21(context) { qrDataPublish.onNext(it) }
} else {
ScannerView19(context) { qrDataPublish.onNext(it) }
}
scannerView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
addView(scannerView)
this.scannerView = (scannerView as ScannerView)
}
override fun start(lifecycleOwner: LifecycleOwner) {
scannerView.start(lifecycleOwner)
lifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) {
qrDataPublish.onComplete()
}
})
}
}

Wyświetl plik

@ -0,0 +1,10 @@
package org.signal.qr
import androidx.lifecycle.LifecycleOwner
/**
* Common interface for interacting with QR scanning views.
*/
interface ScannerView {
fun start(lifecycleOwner: LifecycleOwner)
}

Wyświetl plik

@ -0,0 +1,49 @@
package org.signal.qr
import android.annotation.SuppressLint
import android.content.Context
import android.widget.FrameLayout
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import org.signal.qr.kitkat.QrCameraView
import org.signal.qr.kitkat.ScanListener
import org.signal.qr.kitkat.ScanningThread
/**
* API19 version of QR scanning. Uses deprecated camera APIs.
*/
@SuppressLint("ViewConstructor")
internal class ScannerView19 constructor(
context: Context,
private val scanListener: ScanListener
) : FrameLayout(context), ScannerView {
private var scanningThread: ScanningThread? = null
private val cameraView: QrCameraView
init {
cameraView = QrCameraView(context)
cameraView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
addView(cameraView)
}
override fun start(lifecycleOwner: LifecycleOwner) {
lifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
val scanningThread = ScanningThread()
scanningThread.setScanListener(scanListener)
cameraView.onResume()
cameraView.setPreviewCallback(scanningThread)
scanningThread.start()
this@ScannerView19.scanningThread = scanningThread
}
override fun onPause(owner: LifecycleOwner) {
cameraView.onPause()
scanningThread?.stopScanning()
scanningThread = null
}
})
}
}

Wyświetl plik

@ -0,0 +1,103 @@
package org.signal.qr
import android.annotation.SuppressLint
import android.content.Context
import android.widget.FrameLayout
import androidx.annotation.RequiresApi
import androidx.camera.core.Camera
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.core.content.ContextCompat
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import org.signal.core.util.logging.Log
import org.signal.qr.kitkat.ScanListener
import java.util.concurrent.Executors
/**
* API21+ version of QR scanning view. Uses camerax APIs.
*/
@SuppressLint("ViewConstructor")
@RequiresApi(21)
internal class ScannerView21 constructor(
context: Context,
private val listener: ScanListener
) : FrameLayout(context), ScannerView {
private val analyzerExecutor = Executors.newSingleThreadExecutor()
private var cameraProvider: ProcessCameraProvider? = null
private var camera: Camera? = null
private var previewView: PreviewView
private val qrProcessor = QrProcessor()
init {
previewView = PreviewView(context)
previewView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
addView(previewView)
}
override fun start(lifecycleOwner: LifecycleOwner) {
previewView.post {
Log.i(TAG, "Starting")
ProcessCameraProvider.getInstance(context).apply {
addListener({
try {
onCameraProvider(lifecycleOwner, get())
} catch (e: Exception) {
Log.w(TAG, e)
}
}, ContextCompat.getMainExecutor(context))
}
}
lifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) {
cameraProvider = null
camera = null
analyzerExecutor.shutdown()
}
})
}
private fun onCameraProvider(lifecycle: LifecycleOwner, cameraProvider: ProcessCameraProvider?) {
if (cameraProvider == null) {
Log.w(TAG, "Camera provider is null")
return
}
Log.i(TAG, "Initializing use cases")
val preview = Preview.Builder().build()
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
imageAnalysis.setAnalyzer(analyzerExecutor) { proxy ->
val buffer = proxy.planes[0].buffer
val bytes = ByteArray(buffer.capacity())
buffer.get(bytes)
val data: String? = qrProcessor.getScannedData(bytes, proxy.width, proxy.height)
if (data != null) {
listener.onQrDataFound(data)
}
proxy.close()
}
cameraProvider.unbindAll()
camera = cameraProvider.bindToLifecycle(lifecycle, CameraSelector.DEFAULT_BACK_CAMERA, preview, imageAnalysis)
preview.setSurfaceProvider(previewView.surfaceProvider)
this.cameraProvider = cameraProvider
}
companion object {
private val TAG = Log.tag(ScannerView21::class.java)
}
}

Wyświetl plik

@ -0,0 +1,33 @@
package org.signal.qr.kitkat;
import android.content.Context;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private boolean ready;
@SuppressWarnings("deprecation")
public CameraSurfaceView(Context context) {
super(context);
getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
getHolder().addCallback(this);
}
public boolean isReady() {
return ready;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
ready = true;
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
ready = false;
}
}

Wyświetl plik

@ -0,0 +1,109 @@
package org.signal.qr.kitkat;
import android.app.Activity;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
import android.util.DisplayMetrics;
import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
@SuppressWarnings("deprecation")
public class CameraUtils {
private static final String TAG = Log.tag(CameraUtils.class);
/*
* modified from: https://github.com/commonsguy/cwac-camera/blob/master/camera/src/com/commonsware/cwac/camera/CameraUtils.java
*/
public static @Nullable Size getPreferredPreviewSize(int displayOrientation,
int width,
int height,
@NonNull Parameters parameters) {
final int targetWidth = displayOrientation % 180 == 90 ? height : width;
final int targetHeight = displayOrientation % 180 == 90 ? width : height;
final double targetRatio = (double) targetWidth / targetHeight;
Log.d(TAG, String.format(Locale.US,
"getPreferredPreviewSize(%d, %d, %d) -> target %dx%d, AR %.02f",
displayOrientation, width, height,
targetWidth, targetHeight, targetRatio));
List<Size> sizes = parameters.getSupportedPreviewSizes();
List<Size> ideals = new LinkedList<>();
List<Size> bigEnough = new LinkedList<>();
for (Size size : sizes) {
Log.d(TAG, String.format(Locale.US, " %dx%d (%.02f)", size.width, size.height, (float)size.width / size.height));
if (size.height == size.width * targetRatio && size.height >= targetHeight && size.width >= targetWidth) {
ideals.add(size);
Log.d(TAG, " (ideal ratio)");
} else if (size.width >= targetWidth && size.height >= targetHeight) {
bigEnough.add(size);
Log.d(TAG, " (good size, suboptimal ratio)");
}
}
if (!ideals.isEmpty()) return Collections.min(ideals, new AreaComparator());
else if (!bigEnough.isEmpty()) return Collections.min(bigEnough, new AspectRatioComparator(targetRatio));
else return Collections.max(sizes, new AreaComparator());
}
// based on
// http://developer.android.com/reference/android/hardware/Camera.html#setDisplayOrientation(int)
// and http://stackoverflow.com/a/10383164/115145
public static int getCameraDisplayOrientation(@NonNull Activity activity,
@NonNull CameraInfo info)
{
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
int degrees = 0;
DisplayMetrics dm = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(dm);
switch (rotation) {
case Surface.ROTATION_0: degrees = 0; break;
case Surface.ROTATION_90: degrees = 90; break;
case Surface.ROTATION_180: degrees = 180; break;
case Surface.ROTATION_270: degrees = 270; break;
}
if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
return (360 - ((info.orientation + degrees) % 360)) % 360;
} else {
return (info.orientation - degrees + 360) % 360;
}
}
private static class AreaComparator implements Comparator<Size> {
@Override
public int compare(Size lhs, Size rhs) {
return Long.signum(lhs.width * lhs.height - rhs.width * rhs.height);
}
}
private static class AspectRatioComparator extends AreaComparator {
private final double target;
public AspectRatioComparator(double target) {
this.target = target;
}
@Override
public int compare(Size lhs, Size rhs) {
final double lhsDiff = Math.abs(target - (double) lhs.width / lhs.height);
final double rhsDiff = Math.abs(target - (double) rhs.width / rhs.height);
if (lhsDiff < rhsDiff) return -1;
else if (lhsDiff > rhsDiff) return 1;
else return super.compare(lhs, rhs);
}
}
}

Wyświetl plik

@ -0,0 +1,472 @@
/*
Copyright (c) 2013-2014 CommonsWare, LLC
Portions Copyright (C) 2007 The Android Open Source Project
<p>
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.signal.qr.kitkat;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.view.OrientationEventListener;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
@SuppressWarnings("deprecation")
public class QrCameraView extends ViewGroup {
private static final String TAG = Log.tag(QrCameraView.class);
private final CameraSurfaceView surface;
private final OnOrientationChange onOrientationChange;
private volatile Optional<Camera> camera = Optional.empty();
private final int cameraId = CameraInfo.CAMERA_FACING_BACK;
private volatile int displayOrientation = -1;
private @NonNull State state = State.PAUSED;
private @Nullable Size previewSize;
private final List<CameraViewListener> listeners = Collections.synchronizedList(new LinkedList<>());
private int outputOrientation = -1;
public QrCameraView(Context context) {
this(context, null);
}
public QrCameraView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public QrCameraView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setBackgroundColor(Color.BLACK);
surface = new CameraSurfaceView(getContext());
onOrientationChange = new OnOrientationChange(context.getApplicationContext());
addView(surface);
}
public void onResume() {
if (state != State.PAUSED) return;
state = State.RESUMED;
Log.i(TAG, "onResume() queued");
enqueueTask(new SerialAsyncTask<Void>() {
@Override
protected
@Nullable
Void onRunBackground() {
try {
long openStartMillis = System.currentTimeMillis();
camera = Optional.ofNullable(Camera.open(cameraId));
Log.i(TAG, "camera.open() -> " + (System.currentTimeMillis() - openStartMillis) + "ms");
synchronized (QrCameraView.this) {
QrCameraView.this.notifyAll();
}
camera.ifPresent(value -> onCameraReady(value));
} catch (Exception e) {
Log.w(TAG, e);
}
return null;
}
@Override
protected void onPostMain(Void avoid) {
if (!camera.isPresent()) {
Log.w(TAG, "tried to open camera but got null");
for (CameraViewListener listener : listeners) {
listener.onCameraFail();
}
return;
}
if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
onOrientationChange.enable();
}
Log.i(TAG, "onResume() completed");
}
});
}
public void onPause() {
if (state == State.PAUSED) return;
state = State.PAUSED;
Log.i(TAG, "onPause() queued");
enqueueTask(new SerialAsyncTask<Void>() {
private Optional<Camera> cameraToDestroy;
@Override
protected void onPreMain() {
cameraToDestroy = camera;
camera = Optional.empty();
}
@Override
protected Void onRunBackground() {
if (cameraToDestroy.isPresent()) {
try {
stopPreview();
cameraToDestroy.get().setPreviewCallback(null);
cameraToDestroy.get().release();
Log.w(TAG, "released old camera instance");
} catch (Exception e) {
Log.w(TAG, e);
}
}
return null;
}
@Override protected void onPostMain(Void avoid) {
onOrientationChange.disable();
displayOrientation = -1;
outputOrientation = -1;
removeView(surface);
addView(surface);
Log.i(TAG, "onPause() completed");
}
});
for (CameraViewListener listener : listeners) {
listener.onCameraStop();
}
}
public boolean isStarted() {
return state != State.PAUSED;
}
@SuppressWarnings("SuspiciousNameCombination")
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int width = r - l;
final int height = b - t;
final int previewWidth;
final int previewHeight;
if (camera.isPresent() && previewSize != null) {
if (displayOrientation == 90 || displayOrientation == 270) {
previewWidth = previewSize.height;
previewHeight = previewSize.width;
} else {
previewWidth = previewSize.width;
previewHeight = previewSize.height;
}
} else {
previewWidth = width;
previewHeight = height;
}
if (previewHeight == 0 || previewWidth == 0) {
Log.w(TAG, "skipping layout due to zero-width/height preview size");
return;
}
if (width * previewHeight > height * previewWidth) {
final int scaledChildHeight = previewHeight * width / previewWidth;
surface.layout(0, (height - scaledChildHeight) / 2, width, (height + scaledChildHeight) / 2);
} else {
final int scaledChildWidth = previewWidth * height / previewHeight;
surface.layout((width - scaledChildWidth) / 2, 0, (width + scaledChildWidth) / 2, height);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Log.i(TAG, "onSizeChanged(" + oldw + "x" + oldh + " -> " + w + "x" + h + ")");
super.onSizeChanged(w, h, oldw, oldh);
camera.ifPresent(value -> startPreview(value.getParameters()));
}
public void addListener(@NonNull CameraViewListener listener) {
listeners.add(listener);
}
public void setPreviewCallback(final @NonNull PreviewCallback previewCallback) {
enqueueTask(new PostInitializationTask<Void>() {
@Override
protected void onPostMain(Void avoid) {
camera.ifPresent(value -> value.setPreviewCallback((data, camera) -> {
if (!QrCameraView.this.camera.isPresent()) {
return;
}
final int rotation = getCameraPictureOrientation();
final Size previewSize = camera.getParameters().getPreviewSize();
if (data != null) {
previewCallback.onPreviewFrame(new PreviewFrame(data, previewSize.width, previewSize.height, rotation));
}
}));
}
});
}
private void onCameraReady(final @NonNull Camera camera) {
final Parameters parameters = camera.getParameters();
parameters.setRecordingHint(true);
final List<String> focusModes = parameters.getSupportedFocusModes();
if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
} else if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
}
displayOrientation = CameraUtils.getCameraDisplayOrientation(getActivity(), getCameraInfo());
camera.setDisplayOrientation(displayOrientation);
camera.setParameters(parameters);
enqueueTask(new PostInitializationTask<Void>() {
@Override
protected Void onRunBackground() {
try {
camera.setPreviewDisplay(surface.getHolder());
startPreview(parameters);
} catch (Exception e) {
Log.w(TAG, "couldn't set preview display", e);
}
return null;
}
});
}
private void startPreview(final @NonNull Parameters parameters) {
camera.ifPresent(camera -> {
try {
final Size preferredPreviewSize = getPreferredPreviewSize(parameters);
if (preferredPreviewSize != null && !parameters.getPreviewSize().equals(preferredPreviewSize)) {
Log.i(TAG, "starting preview with size " + preferredPreviewSize.width + "x" + preferredPreviewSize.height);
if (state == State.ACTIVE) stopPreview();
previewSize = preferredPreviewSize;
parameters.setPreviewSize(preferredPreviewSize.width, preferredPreviewSize.height);
camera.setParameters(parameters);
} else {
previewSize = parameters.getPreviewSize();
}
long previewStartMillis = System.currentTimeMillis();
camera.startPreview();
Log.i(TAG, "camera.startPreview() -> " + (System.currentTimeMillis() - previewStartMillis) + "ms");
state = State.ACTIVE;
ThreadUtil.runOnMain(() -> {
requestLayout();
for (CameraViewListener listener : listeners) {
listener.onCameraStart();
}
});
} catch (Exception e) {
Log.w(TAG, e);
}
});
}
private void stopPreview() {
camera.ifPresent(camera -> {
try {
camera.stopPreview();
state = State.RESUMED;
} catch (Exception e) {
Log.w(TAG, e);
}
});
}
private Size getPreferredPreviewSize(@NonNull Parameters parameters) {
return CameraUtils.getPreferredPreviewSize(displayOrientation,
getMeasuredWidth(),
getMeasuredHeight(),
parameters);
}
private int getCameraPictureOrientation() {
if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
outputOrientation = getCameraPictureRotation(getActivity().getWindowManager()
.getDefaultDisplay()
.getOrientation());
} else if (getCameraInfo().facing == CameraInfo.CAMERA_FACING_FRONT) {
outputOrientation = (360 - displayOrientation) % 360;
} else {
outputOrientation = displayOrientation;
}
return outputOrientation;
}
private @NonNull CameraInfo getCameraInfo() {
final CameraInfo info = new CameraInfo();
Camera.getCameraInfo(cameraId, info);
return info;
}
// XXX this sucks
private Activity getActivity() {
return (Activity) getContext();
}
public int getCameraPictureRotation(int orientation) {
final CameraInfo info = getCameraInfo();
final int rotation;
orientation = (orientation + 45) / 90 * 90;
if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
rotation = (info.orientation - orientation + 360) % 360;
} else {
rotation = (info.orientation + orientation) % 360;
}
return rotation;
}
private class OnOrientationChange extends OrientationEventListener {
public OnOrientationChange(Context context) {
super(context);
disable();
}
@Override
public void onOrientationChanged(int orientation) {
camera.ifPresent(camera -> {
if (orientation != ORIENTATION_UNKNOWN) {
int newOutputOrientation = getCameraPictureRotation(orientation);
if (newOutputOrientation != outputOrientation) {
outputOrientation = newOutputOrientation;
Parameters params = camera.getParameters();
params.setRotation(outputOrientation);
try {
camera.setParameters(params);
} catch (Exception e) {
Log.e(TAG, "Exception updating camera parameters in orientation change", e);
}
}
}
});
}
}
private void enqueueTask(SerialAsyncTask<?> job) {
AsyncTask.SERIAL_EXECUTOR.execute(job);
}
public static abstract class SerialAsyncTask<Result> implements Runnable {
@Override
public final void run() {
if (!onWait()) {
Log.w(TAG, "skipping task, preconditions not met in onWait()");
return;
}
ThreadUtil.runOnMainSync(this::onPreMain);
final Result result = onRunBackground();
ThreadUtil.runOnMainSync(() -> onPostMain(result));
}
protected boolean onWait() {return true;}
protected void onPreMain() {}
protected Result onRunBackground() {return null;}
protected void onPostMain(Result result) {}
}
private abstract class PostInitializationTask<Result> extends SerialAsyncTask<Result> {
@Override protected boolean onWait() {
synchronized (QrCameraView.this) {
if (!camera.isPresent()) {
return false;
}
while (getMeasuredHeight() <= 0 || getMeasuredWidth() <= 0 || !surface.isReady()) {
Log.i(TAG, String.format("waiting. surface ready? %s", surface.isReady()));
waitFor();
}
return true;
}
}
}
private void waitFor() {
try {
wait(0);
} catch (InterruptedException ie) {
throw new AssertionError(ie);
}
}
public interface CameraViewListener {
void onImageCapture(@NonNull final byte[] imageBytes);
void onCameraFail();
void onCameraStart();
void onCameraStop();
}
public interface PreviewCallback {
void onPreviewFrame(@NonNull PreviewFrame frame);
}
public static class PreviewFrame {
private final @NonNull byte[] data;
private final int width;
private final int height;
private final int orientation;
public PreviewFrame(@NonNull byte[] data, int width, int height, int orientation) {
this.data = data;
this.width = width;
this.height = height;
this.orientation = orientation;
}
public @NonNull byte[] getData() {
return data;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public int getOrientation() {
return orientation;
}
}
private enum State {
PAUSED, RESUMED, ACTIVE
}
}

Wyświetl plik

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.qr;
package org.signal.qr.kitkat;
import androidx.annotation.NonNull;

Wyświetl plik

@ -0,0 +1,88 @@
package org.signal.qr.kitkat;
import androidx.annotation.NonNull;
import com.google.zxing.DecodeHintType;
import org.signal.core.util.logging.Log;
import org.signal.qr.QrProcessor;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import static org.signal.qr.kitkat.QrCameraView.PreviewCallback;
import static org.signal.qr.kitkat.QrCameraView.PreviewFrame;
public class ScanningThread extends Thread implements PreviewCallback {
private static final String TAG = Log.tag(ScanningThread.class);
private final QrProcessor processor = new QrProcessor();
private final AtomicReference<ScanListener> scanListener = new AtomicReference<>();
private final Map<DecodeHintType, String> hints = new HashMap<>();
private boolean scanning = true;
private PreviewFrame previewFrame;
public void setCharacterSet(String characterSet) {
hints.put(DecodeHintType.CHARACTER_SET, characterSet);
}
public void setScanListener(ScanListener scanListener) {
this.scanListener.set(scanListener);
}
@Override
public void onPreviewFrame(@NonNull PreviewFrame previewFrame) {
try {
synchronized (this) {
this.previewFrame = previewFrame;
this.notify();
}
} catch (RuntimeException e) {
Log.w(TAG, e);
}
}
@Override
public void run() {
while (true) {
PreviewFrame ourFrame;
synchronized (this) {
while (scanning && previewFrame == null) {
waitFor();
}
if (!scanning) return;
else ourFrame = previewFrame;
previewFrame = null;
}
String data = processor.getScannedData(ourFrame.getData(), ourFrame.getWidth(), ourFrame.getHeight());
ScanListener scanListener = this.scanListener.get();
if (data != null && scanListener != null) {
scanListener.onQrDataFound(data);
return;
}
}
}
public void stopScanning() {
synchronized (this) {
scanning = false;
notify();
}
}
private void waitFor() {
try {
wait(0);
} catch (InterruptedException ie) {
throw new AssertionError(ie);
}
}
}

Wyświetl plik

@ -18,6 +18,8 @@ include ':spinner'
include ':spinner-app'
include ':contacts'
include ':contacts-app'
include ':qr'
include ':qr-app'
project(':app').name = 'Signal-Android'
project(':paging').projectDir = file('paging/lib')
@ -40,6 +42,9 @@ project(':spinner-app').projectDir = file('spinner/app')
project(':contacts').projectDir = file('contacts/lib')
project(':contacts-app').projectDir = file('contacts/app')
project(':qr').projectDir = file('qr/lib')
project(':qr-app').projectDir = file('qr/app')
rootProject.name='Signal'
apply from: 'dependencies.gradle'

Wyświetl plik

@ -0,0 +1,48 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'org.jlleitschuh.gradle.ktlint'
android {
buildToolsVersion BUILD_TOOL_VERSION
compileSdkVersion COMPILE_SDK
defaultConfig {
minSdkVersion MINIMUM_SDK
targetSdkVersion TARGET_SDK
multiDexEnabled true
}
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JAVA_VERSION
targetCompatibility JAVA_VERSION
}
kotlinOptions {
jvmTarget = '1.8'
}
lintOptions {
disable 'InvalidVectorPath'
}
}
ktlint {
// Use a newer version to resolve https://github.com/JLLeitschuh/ktlint-gradle/issues/507
version = "0.43.2"
}
dependencies {
lintChecks project(':lintchecks')
implementation project(':core-util')
coreLibraryDesugaring libs.android.tools.desugar
implementation libs.androidx.core.ktx
implementation libs.androidx.fragment.ktx
implementation libs.androidx.annotation
implementation libs.androidx.appcompat
implementation libs.kotlin.stdlib.jdk8
}

Wyświetl plik

@ -0,0 +1,48 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'org.jlleitschuh.gradle.ktlint'
android {
buildToolsVersion BUILD_TOOL_VERSION
compileSdkVersion COMPILE_SDK
defaultConfig {
versionCode 1
versionName "1.0"
minSdkVersion 19
targetSdkVersion TARGET_SDK
multiDexEnabled true
}
kotlinOptions {
jvmTarget = '1.8'
}
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JAVA_VERSION
targetCompatibility JAVA_VERSION
}
}
ktlint {
// Use a newer version to resolve https://github.com/JLLeitschuh/ktlint-gradle/issues/507
version = "0.43.2"
}
dependencies {
coreLibraryDesugaring libs.android.tools.desugar
implementation "androidx.activity:activity-ktx:1.2.2"
implementation libs.androidx.appcompat
implementation libs.material.material
implementation libs.androidx.constraintlayout
implementation libs.kotlin.stdlib.jdk8
testImplementation testLibs.junit.junit
implementation project(':core-util')
}