diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3bf92947d0..426dbd7c37 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -380,7 +380,7 @@
android:label="@string/AndroidManifest__change_passphrase"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
-
diff --git a/app/src/main/java/org/thoughtcrime/securesms/DeviceActivity.java b/app/src/main/java/org/thoughtcrime/securesms/DeviceActivity.java
index 961499eee9..b97d6088ca 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/DeviceActivity.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/DeviceActivity.java
@@ -120,7 +120,7 @@ public class DeviceActivity extends PassphraseRequiredActivity
}
@Override
- public void onQrDataFound(final String data) {
+ public void onQrDataFound(@NonNull final String data) {
ThreadUtil.runOnMain(() -> {
((Vibrator)getSystemService(Context.VIBRATOR_SERVICE)).vibrate(50);
Uri uri = Uri.parse(data);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java b/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java
deleted file mode 100644
index 8cc2963ac8..0000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java
+++ /dev/null
@@ -1,774 +0,0 @@
-/*
- * Copyright (C) 2016-2017 Open Whisper Systems
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-package org.thoughtcrime.securesms;
-
-import android.Manifest;
-import android.animation.TypeEvaluator;
-import android.animation.ValueAnimator;
-import android.annotation.SuppressLint;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.PorterDuff;
-import android.graphics.drawable.BitmapDrawable;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Vibrator;
-import android.text.Html;
-import android.text.TextUtils;
-import android.text.method.LinkMovementMethod;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.animation.Animation;
-import android.view.animation.AnticipateInterpolator;
-import android.view.animation.ScaleAnimation;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.ScrollView;
-import android.widget.TextSwitcher;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import androidx.annotation.DrawableRes;
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;
-import androidx.core.view.OneShotPreDrawListener;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentTransaction;
-import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
-
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-
-import org.signal.core.util.ThreadUtil;
-import org.signal.core.util.concurrent.SignalExecutors;
-import org.signal.core.util.logging.Log;
-import org.thoughtcrime.securesms.components.ShapeScrim;
-import org.thoughtcrime.securesms.components.camera.CameraView;
-import org.thoughtcrime.securesms.crypto.ReentrantSessionLock;
-import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
-import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
-import org.thoughtcrime.securesms.database.IdentityDatabase;
-import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
-import org.thoughtcrime.securesms.database.model.IdentityRecord;
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
-import org.thoughtcrime.securesms.jobs.MultiDeviceVerifiedUpdateJob;
-import org.thoughtcrime.securesms.permissions.Permissions;
-import org.thoughtcrime.securesms.qr.QrCode;
-import org.thoughtcrime.securesms.qr.ScanListener;
-import org.thoughtcrime.securesms.qr.ScanningThread;
-import org.thoughtcrime.securesms.recipients.LiveRecipient;
-import org.thoughtcrime.securesms.recipients.Recipient;
-import org.thoughtcrime.securesms.recipients.RecipientId;
-import org.thoughtcrime.securesms.storage.StorageSyncHelper;
-import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
-import org.thoughtcrime.securesms.util.DynamicTheme;
-import org.thoughtcrime.securesms.util.FeatureFlags;
-import org.thoughtcrime.securesms.util.IdentityUtil;
-import org.thoughtcrime.securesms.util.TextSecurePreferences;
-import org.thoughtcrime.securesms.util.Util;
-import org.thoughtcrime.securesms.util.ViewUtil;
-import org.whispersystems.libsignal.IdentityKey;
-import org.whispersystems.libsignal.fingerprint.Fingerprint;
-import org.whispersystems.libsignal.fingerprint.FingerprintVersionMismatchException;
-import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator;
-import org.whispersystems.signalservice.api.SignalSessionLock;
-import org.whispersystems.signalservice.api.util.UuidUtil;
-
-import java.nio.charset.Charset;
-import java.util.Locale;
-
-/**
- * Activity for verifying identity keys.
- *
- * @author Moxie Marlinspike
- */
-@SuppressLint("StaticFieldLeak")
-public class VerifyIdentityActivity extends PassphraseRequiredActivity implements ScanListener, View.OnClickListener {
-
- private static final String TAG = Log.tag(VerifyIdentityActivity.class);
-
- private static final String RECIPIENT_EXTRA = "recipient_id";
- private static final String IDENTITY_EXTRA = "recipient_identity";
- private static final String VERIFIED_EXTRA = "verified_state";
-
- private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
-
- private final VerifyDisplayFragment displayFragment = new VerifyDisplayFragment();
- private final VerifyScanFragment scanFragment = new VerifyScanFragment();
-
- public static Intent newIntent(@NonNull Context context,
- @NonNull IdentityRecord identityRecord)
- {
- return newIntent(context,
- identityRecord.getRecipientId(),
- identityRecord.getIdentityKey(),
- identityRecord.getVerifiedStatus() == IdentityDatabase.VerifiedStatus.VERIFIED);
- }
-
- public static Intent newIntent(@NonNull Context context,
- @NonNull IdentityRecord identityRecord,
- boolean verified)
- {
- return newIntent(context,
- identityRecord.getRecipientId(),
- identityRecord.getIdentityKey(),
- verified);
- }
-
- public static Intent newIntent(@NonNull Context context,
- @NonNull RecipientId recipientId,
- @NonNull IdentityKey identityKey,
- boolean verified)
- {
- Intent intent = new Intent(context, VerifyIdentityActivity.class);
-
- intent.putExtra(RECIPIENT_EXTRA, recipientId);
- intent.putExtra(IDENTITY_EXTRA, new IdentityKeyParcelable(identityKey));
- intent.putExtra(VERIFIED_EXTRA, verified);
-
- return intent;
- }
-
- @Override
- public void onPreCreate() {
- dynamicTheme.onCreate(this);
- }
-
- @Override
- protected void onCreate(Bundle state, boolean ready) {
- Bundle extras = new Bundle();
- extras.putParcelable(VerifyDisplayFragment.RECIPIENT_ID, getIntent().getParcelableExtra(RECIPIENT_EXTRA));
- extras.putParcelable(VerifyDisplayFragment.REMOTE_IDENTITY, getIntent().getParcelableExtra(IDENTITY_EXTRA));
- extras.putParcelable(VerifyDisplayFragment.LOCAL_IDENTITY, new IdentityKeyParcelable(IdentityKeyUtil.getIdentityKey(this)));
- extras.putString(VerifyDisplayFragment.LOCAL_NUMBER, Recipient.self().requireE164());
- extras.putBoolean(VerifyDisplayFragment.VERIFIED_STATE, getIntent().getBooleanExtra(VERIFIED_EXTRA, false));
-
- scanFragment.setScanListener(this);
- displayFragment.setClickListener(this);
-
- initFragment(android.R.id.content, displayFragment, Locale.getDefault(), extras);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home: finish(); return true;
- }
-
- return false;
- }
-
- @Override
- public void onQrDataFound(final String data) {
- ThreadUtil.runOnMain(() -> {
- ((Vibrator)getSystemService(Context.VIBRATOR_SERVICE)).vibrate(50);
-
- getSupportFragmentManager().popBackStack();
- displayFragment.setScannedFingerprint(data);
- });
- }
-
- @Override
- public void onClick(View v) {
- Permissions.with(this)
- .request(Manifest.permission.CAMERA)
- .ifNecessary()
- .withPermanentDenialDialog(getString(R.string.VerifyIdentityActivity_signal_needs_the_camera_permission_in_order_to_scan_a_qr_code_but_it_has_been_permanently_denied))
- .onAllGranted(() -> {
- FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
- transaction.setCustomAnimations(R.anim.slide_from_top, R.anim.slide_to_bottom,
- R.anim.slide_from_bottom, R.anim.slide_to_top);
-
- transaction.replace(android.R.id.content, scanFragment)
- .addToBackStack(null)
- .commitAllowingStateLoss();
- })
- .onAnyDenied(() -> Toast.makeText(this, R.string.VerifyIdentityActivity_unable_to_scan_qr_code_without_camera_permission, Toast.LENGTH_LONG).show())
- .execute();
- }
-
- @SuppressLint("MissingSuperCall")
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
- }
-
- public static class VerifyDisplayFragment extends Fragment implements ViewTreeObserver.OnScrollChangedListener {
-
- public static final String RECIPIENT_ID = "recipient_id";
- public static final String REMOTE_NUMBER = "remote_number";
- public static final String REMOTE_IDENTITY = "remote_identity";
- public static final String LOCAL_IDENTITY = "local_identity";
- public static final String LOCAL_NUMBER = "local_number";
- public static final String VERIFIED_STATE = "verified_state";
-
- private LiveRecipient recipient;
- private IdentityKey localIdentity;
- private IdentityKey remoteIdentity;
- private Fingerprint fingerprint;
-
- private Toolbar toolbar;
- private ScrollView scrollView;
- private View container;
- private View numbersContainer;
- private View loading;
- private View qrCodeContainer;
- private ImageView qrCode;
- private ImageView qrVerified;
- private TextSwitcher tapLabel;
- private TextView description;
- private View.OnClickListener clickListener;
- private Button verifyButton;
- private View toolbarShadow;
- private View bottomShadow;
-
- private TextView[] codes = new TextView[12];
- private boolean animateSuccessOnDraw = false;
- private boolean animateFailureOnDraw = false;
- private boolean currentVerifiedState = false;
-
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
- this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.verify_display_fragment);
- this.toolbar = container.findViewById(R.id.toolbar);
- this.scrollView = container.findViewById(R.id.scroll_view);
- this.numbersContainer = container.findViewById(R.id.number_table);
- this.loading = container.findViewById(R.id.loading);
- this.qrCodeContainer = container.findViewById(R.id.qr_code_container);
- this.qrCode = container.findViewById(R.id.qr_code);
- this.verifyButton = container.findViewById(R.id.verify_button);
- this.qrVerified = container.findViewById(R.id.qr_verified);
- this.description = container.findViewById(R.id.description);
- this.tapLabel = container.findViewById(R.id.tap_label);
- this.toolbarShadow = container.findViewById(R.id.toolbar_shadow);
- this.bottomShadow = container.findViewById(R.id.verify_identity_bottom_shadow);
- this.codes[0] = container.findViewById(R.id.code_first);
- this.codes[1] = container.findViewById(R.id.code_second);
- this.codes[2] = container.findViewById(R.id.code_third);
- this.codes[3] = container.findViewById(R.id.code_fourth);
- this.codes[4] = container.findViewById(R.id.code_fifth);
- this.codes[5] = container.findViewById(R.id.code_sixth);
- this.codes[6] = container.findViewById(R.id.code_seventh);
- this.codes[7] = container.findViewById(R.id.code_eighth);
- this.codes[8] = container.findViewById(R.id.code_ninth);
- this.codes[9] = container.findViewById(R.id.code_tenth);
- this.codes[10] = container.findViewById(R.id.code_eleventh);
- this.codes[11] = container.findViewById(R.id.code_twelth);
-
- this.qrCodeContainer.setOnClickListener(clickListener);
- this.registerForContextMenu(numbersContainer);
-
- updateVerifyButton(getArguments().getBoolean(VERIFIED_STATE, false), false);
- this.verifyButton.setOnClickListener((button -> updateVerifyButton(!currentVerifiedState, true)));
-
- this.scrollView.getViewTreeObserver().addOnScrollChangedListener(this);
-
- ((AppCompatActivity)requireActivity()).setSupportActionBar(toolbar);
- ((AppCompatActivity)requireActivity()).setTitle(R.string.AndroidManifest__verify_safety_number);
-
- return container;
- }
-
- @Override public void onDestroyView() {
- this.scrollView.getViewTreeObserver().removeOnScrollChangedListener(this);
- super.onDestroyView();
- }
-
- @Override
- public void onCreate(Bundle bundle) {
- super.onCreate(bundle);
-
- RecipientId recipientId = getArguments().getParcelable(RECIPIENT_ID);
- IdentityKeyParcelable localIdentityParcelable = getArguments().getParcelable(LOCAL_IDENTITY);
- IdentityKeyParcelable remoteIdentityParcelable = getArguments().getParcelable(REMOTE_IDENTITY);
-
- if (recipientId == null) throw new AssertionError("RecipientId required");
- if (localIdentityParcelable == null) throw new AssertionError("local identity required");
- if (remoteIdentityParcelable == null) throw new AssertionError("remote identity required");
-
- this.localIdentity = localIdentityParcelable.get();
- this.recipient = Recipient.live(recipientId);
- this.remoteIdentity = remoteIdentityParcelable.get();
-
- int version;
- byte[] localId;
- byte[] remoteId;
-
- //noinspection WrongThread
- Recipient resolved = recipient.resolve();
-
- if (FeatureFlags.verifyV2() && resolved.getAci().isPresent()) {
- Log.i(TAG, "Using UUID (version 2).");
- version = 2;
- localId = Recipient.self().requireAci().toByteArray();
- remoteId = resolved.requireAci().toByteArray();
- } else if (!FeatureFlags.verifyV2() && resolved.getE164().isPresent()) {
- Log.i(TAG, "Using E164 (version 1).");
- version = 1;
- localId = Recipient.self().requireE164().getBytes();
- remoteId = resolved.requireE164().getBytes();
- } else {
- Log.w(TAG, String.format(Locale.ENGLISH, "Could not show proper verification! verifyV2: %s, hasUuid: %s, hasE164: %s", FeatureFlags.verifyV2(), resolved.getAci().isPresent(), resolved.getE164().isPresent()));
- new MaterialAlertDialogBuilder(requireContext())
- .setMessage(getString(R.string.VerifyIdentityActivity_you_must_first_exchange_messages_in_order_to_view, resolved.getDisplayName(requireContext())))
- .setPositiveButton(android.R.string.ok, (dialog, which) -> requireActivity().finish())
- .setOnDismissListener(dialog -> {
- requireActivity().finish();
- dialog.dismiss();
- })
- .show();
- return;
- }
-
- this.recipient.observe(this, this::setRecipientText);
-
- new AsyncTask() {
- @Override
- protected Fingerprint doInBackground(Void... params) {
- return new NumericFingerprintGenerator(5200).createFor(version,
- localId, localIdentity,
- remoteId, remoteIdentity);
- }
-
- @Override
- protected void onPostExecute(Fingerprint fingerprint) {
- if (getActivity() == null) return;
- VerifyDisplayFragment.this.fingerprint = fingerprint;
- setFingerprintViews(fingerprint, true);
- getActivity().supportInvalidateOptionsMenu();
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
-
- setHasOptionsMenu(true);
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- setRecipientText(recipient.get());
-
- if (fingerprint != null) {
- setFingerprintViews(fingerprint, false);
- }
-
- if (animateSuccessOnDraw) {
- animateSuccessOnDraw = false;
- animateVerifiedSuccess();
- } else if (animateFailureOnDraw) {
- animateFailureOnDraw = false;
- animateVerifiedFailure();
- }
-
- ThreadUtil.postToMain(this::onScrollChanged);
- }
-
- @Override
- public void onCreateContextMenu(ContextMenu menu, View view,
- ContextMenuInfo menuInfo)
- {
- super.onCreateContextMenu(menu, view, menuInfo);
-
- if (fingerprint != null) {
- MenuInflater inflater = getActivity().getMenuInflater();
- inflater.inflate(R.menu.verify_display_fragment_context_menu, menu);
- }
- }
-
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- if (fingerprint == null) return super.onContextItemSelected(item);
-
- switch (item.getItemId()) {
- case R.id.menu_copy: handleCopyToClipboard(fingerprint, codes.length); return true;
- case R.id.menu_compare: handleCompareWithClipboard(fingerprint); return true;
- default: return super.onContextItemSelected(item);
- }
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- super.onCreateOptionsMenu(menu, inflater);
-
- if (fingerprint != null) {
- inflater.inflate(R.menu.verify_identity, menu);
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.verify_identity__share: handleShare(fingerprint, codes.length); return true;
- }
-
- return false;
- }
-
- public void setScannedFingerprint(String scanned) {
- try {
- if (fingerprint.getScannableFingerprint().compareTo(scanned.getBytes("ISO-8859-1"))) {
- this.animateSuccessOnDraw = true;
- } else {
- this.animateFailureOnDraw = true;
- }
- } catch (FingerprintVersionMismatchException e) {
- Log.w(TAG, e);
- if (e.getOurVersion() < e.getTheirVersion()) {
- Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_your_contact_is_running_a_newer_version_of_Signal, Toast.LENGTH_LONG).show();
- } else {
- Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_your_contact_is_running_an_old_version_of_signal, Toast.LENGTH_LONG).show();
- }
- this.animateFailureOnDraw = true;
- } catch (Exception e) {
- Log.w(TAG, e);
- Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_the_scanned_qr_code_is_not_a_correctly_formatted_safety_number, Toast.LENGTH_LONG).show();
- this.animateFailureOnDraw = true;
- }
- }
-
- public void setClickListener(View.OnClickListener listener) {
- this.clickListener = listener;
- }
-
- private @NonNull String getFormattedSafetyNumbers(@NonNull Fingerprint fingerprint, int segmentCount) {
- String[] segments = getSegments(fingerprint, segmentCount);
- StringBuilder result = new StringBuilder();
-
- for (int i = 0; i < segments.length; i++) {
- result.append(segments[i]);
-
- if (i != segments.length - 1) {
- if (((i+1) % 4) == 0) result.append('\n');
- else result.append(' ');
- }
- }
-
- return result.toString();
- }
-
- private void handleCopyToClipboard(Fingerprint fingerprint, int segmentCount) {
- Util.writeTextToClipboard(getActivity(), getFormattedSafetyNumbers(fingerprint, segmentCount));
- }
-
- private void handleCompareWithClipboard(Fingerprint fingerprint) {
- String clipboardData = Util.readTextFromClipboard(getActivity());
-
- if (clipboardData == null) {
- Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_no_safety_number_to_compare_was_found_in_the_clipboard, Toast.LENGTH_LONG).show();
- return;
- }
-
- String numericClipboardData = clipboardData.replaceAll("\\D", "");
-
- if (TextUtils.isEmpty(numericClipboardData) || numericClipboardData.length() != 60) {
- Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_no_safety_number_to_compare_was_found_in_the_clipboard, Toast.LENGTH_LONG).show();
- return;
- }
-
- if (fingerprint.getDisplayableFingerprint().getDisplayText().equals(numericClipboardData)) {
- animateVerifiedSuccess();
- } else {
- animateVerifiedFailure();
- }
- }
-
- private void handleShare(@NonNull Fingerprint fingerprint, int segmentCount) {
- String shareString =
- getString(R.string.VerifyIdentityActivity_our_signal_safety_number) + "\n" +
- getFormattedSafetyNumbers(fingerprint, segmentCount) + "\n";
-
- Intent intent = new Intent();
- intent.setAction(Intent.ACTION_SEND);
- intent.putExtra(Intent.EXTRA_TEXT, shareString);
- intent.setType("text/plain");
-
- try {
- startActivity(Intent.createChooser(intent, getString(R.string.VerifyIdentityActivity_share_safety_number_via)));
- } catch (ActivityNotFoundException e) {
- Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_no_app_to_share_to, Toast.LENGTH_LONG).show();
- }
- }
-
- private void setRecipientText(Recipient recipient) {
- description.setText(Html.fromHtml(String.format(getActivity().getString(R.string.verify_display_fragment__to_verify_the_security_of_your_end_to_end_encryption_with_s), recipient.getDisplayName(getContext()))));
- description.setMovementMethod(LinkMovementMethod.getInstance());
- }
-
- private void setFingerprintViews(Fingerprint fingerprint, boolean animate) {
- String[] segments = getSegments(fingerprint, codes.length);
-
- for (int i=0;i() {
- public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
- return Math.round(startValue + (endValue - startValue) * fraction);
- }
- });
-
- valueAnimator.setDuration(1000);
- valueAnimator.start();
- }
-
- private String[] getSegments(Fingerprint fingerprint, int segmentCount) {
- String[] segments = new String[segmentCount];
- String digits = fingerprint.getDisplayableFingerprint().getDisplayText();
- int partSize = digits.length() / segmentCount;
-
- for (int i=0;i {
- try (SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) {
- if (verified) {
- Log.i(TAG, "Saving identity: " + recipientId);
- ApplicationDependencies.getIdentityStore()
- .saveIdentityWithoutSideEffects(recipientId,
- remoteIdentity,
- VerifiedStatus.VERIFIED,
- false,
- System.currentTimeMillis(),
- true);
- } else {
- ApplicationDependencies.getIdentityStore().setVerified(recipientId, remoteIdentity, VerifiedStatus.DEFAULT);
- }
-
- ApplicationDependencies.getJobManager()
- .add(new MultiDeviceVerifiedUpdateJob(recipientId,
- remoteIdentity,
- verified ? VerifiedStatus.VERIFIED
- : VerifiedStatus.DEFAULT));
- StorageSyncHelper.scheduleSyncForDataChange();
-
- IdentityUtil.markIdentityVerified(getActivity(), recipient.get(), verified, false);
- }
- });
- }
- }
-
-
- @Override public void onScrollChanged() {
- if (scrollView.canScrollVertically(-1)) {
- if (toolbarShadow.getVisibility() != View.VISIBLE) {
- ViewUtil.fadeIn(toolbarShadow, 250);
- }
- } else {
- if (toolbarShadow.getVisibility() != View.GONE) {
- ViewUtil.fadeOut(toolbarShadow, 250);
- }
- }
-
- if (scrollView.canScrollVertically(1)) {
- if (bottomShadow.getVisibility() != View.VISIBLE) {
- ViewUtil.fadeIn(bottomShadow, 250);
- }
- } else {
- ViewUtil.fadeOut(bottomShadow, 250);
- }
- }
- }
-
- public static class VerifyScanFragment extends Fragment {
-
- private View container;
- private CameraView cameraView;
- private ShapeScrim cameraScrim;
- private ImageView cameraMarks;
- private ScanningThread scanningThread;
- private ScanListener scanListener;
-
- public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
- this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.verify_scan_fragment);
- this.cameraView = container.findViewById(R.id.scanner);
- this.cameraScrim = container.findViewById(R.id.camera_scrim);
- this.cameraMarks = container.findViewById(R.id.camera_marks);
-
- OneShotPreDrawListener.add(cameraScrim, () -> {
- int width = cameraScrim.getScrimWidth();
- int height = cameraScrim.getScrimHeight();
-
- ViewUtil.updateLayoutParams(cameraMarks, width, height);
- });
-
- return container;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- this.scanningThread = new ScanningThread();
- this.scanningThread.setScanListener(scanListener);
- this.scanningThread.setCharacterSet("ISO-8859-1");
- this.cameraView.onResume();
- this.cameraView.setPreviewCallback(scanningThread);
- this.scanningThread.start();
- }
-
- @Override
- public void onPause() {
- super.onPause();
- this.cameraView.onPause();
- this.scanningThread.stopScanning();
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfiguration) {
- super.onConfigurationChanged(newConfiguration);
- this.cameraView.onPause();
- this.cameraView.onResume();
- this.cameraView.setPreviewCallback(scanningThread);
- }
-
- public void setScanListener(ScanListener listener) {
- if (this.scanningThread != null) scanningThread.setScanListener(listener);
- this.scanListener = listener;
- }
-
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt
index 7e418389d4..de987818a1 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt
@@ -30,7 +30,6 @@ import org.thoughtcrime.securesms.MediaPreviewActivity
import org.thoughtcrime.securesms.MuteDialog
import org.thoughtcrime.securesms.PushContactSelectionActivity
import org.thoughtcrime.securesms.R
-import org.thoughtcrime.securesms.VerifyIdentityActivity
import org.thoughtcrime.securesms.badges.BadgeImageView
import org.thoughtcrime.securesms.badges.Badges
import org.thoughtcrime.securesms.badges.Badges.displayBadges
@@ -83,6 +82,7 @@ import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.ThemeUtil
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog
+import org.thoughtcrime.securesms.verify.VerifyIdentityActivity
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperActivity
private const val REQUEST_CODE_VIEW_CONTACT = 1
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java
index 4c5bb917b8..f451b90ee1 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java
@@ -105,7 +105,7 @@ import org.thoughtcrime.securesms.PromptMmsActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.ShortcutLauncherActivity;
import org.thoughtcrime.securesms.TransportOption;
-import org.thoughtcrime.securesms.VerifyIdentityActivity;
+import org.thoughtcrime.securesms.verify.VerifyIdentityActivity;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.TombstoneAttachment;
import org.thoughtcrime.securesms.audio.AudioRecorder;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java
index d0aa879abe..b662a23c96 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java
@@ -66,13 +66,12 @@ import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-import org.signal.core.util.ThreadUtil;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.VerifyIdentityActivity;
+import org.thoughtcrime.securesms.verify.VerifyIdentityActivity;
import org.thoughtcrime.securesms.components.ConversationScrollToView;
import org.thoughtcrime.securesms.components.ConversationTypingView;
import org.thoughtcrime.securesms.components.TooltipPopup;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java
index cb9f90d2b7..dbeb2e310f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java
@@ -23,7 +23,7 @@ import com.google.common.collect.Sets;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.BindableConversationItem;
import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.VerifyIdentityActivity;
+import org.thoughtcrime.securesms.verify.VerifyIdentityActivity;
import org.thoughtcrime.securesms.conversation.colors.Colorizer;
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart;
import org.thoughtcrime.securesms.conversation.ui.error.EnableCallNotificationSettingsDialog;
@@ -40,7 +40,6 @@ import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.Projection;
import org.thoughtcrime.securesms.util.ProjectionList;
-import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -53,7 +52,6 @@ import java.util.Collection;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
-import java.util.UUID;
import java.util.concurrent.ExecutionException;
public final class ConversationUpdateItem extends FrameLayout
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeDialog.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeDialog.java
index c1141c60be..f2d7a0ec26 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeDialog.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeDialog.java
@@ -26,8 +26,7 @@ import com.annimon.stream.Stream;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.VerifyIdentityActivity;
-import org.thoughtcrime.securesms.database.IdentityDatabase;
+import org.thoughtcrime.securesms.verify.VerifyIdentityActivity;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.model.IdentityRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/qr/ScanListener.java b/app/src/main/java/org/thoughtcrime/securesms/qr/ScanListener.java
index 83faae9907..93224ae18c 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/qr/ScanListener.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/qr/ScanListener.java
@@ -1,5 +1,7 @@
package org.thoughtcrime.securesms.qr;
+import androidx.annotation.NonNull;
+
public interface ScanListener {
- public void onQrDataFound(String data);
+ void onQrDataFound(@NonNull String data);
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java
index abf684a5e5..fd2d73420b 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java
@@ -18,10 +18,9 @@ import androidx.lifecycle.ViewModelProvider;
import org.signal.core.util.ThreadUtil;
import org.thoughtcrime.securesms.BlockUnblockDialog;
import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.VerifyIdentityActivity;
+import org.thoughtcrime.securesms.verify.VerifyIdentityActivity;
import org.thoughtcrime.securesms.components.settings.conversation.ConversationSettingsActivity;
import org.thoughtcrime.securesms.database.GroupDatabase;
-import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.model.IdentityRecord;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.LiveGroup;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/VerifySpan.java b/app/src/main/java/org/thoughtcrime/securesms/util/VerifySpan.java
index 0a3f61d323..7f9d2ccada 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/util/VerifySpan.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/util/VerifySpan.java
@@ -6,7 +6,7 @@ import android.view.View;
import androidx.annotation.NonNull;
-import org.thoughtcrime.securesms.VerifyIdentityActivity;
+import org.thoughtcrime.securesms.verify.VerifyIdentityActivity;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.whispersystems.libsignal.IdentityKey;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyDisplayFragment.java b/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyDisplayFragment.java
new file mode 100644
index 0000000000..2a89ab4d66
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyDisplayFragment.java
@@ -0,0 +1,592 @@
+package org.thoughtcrime.securesms.verify;
+
+import android.animation.TypeEvaluator;
+import android.animation.ValueAnimator;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Bundle;
+import android.text.Html;
+import android.text.TextUtils;
+import android.text.method.LinkMovementMethod;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.animation.Animation;
+import android.view.animation.AnticipateInterpolator;
+import android.view.animation.ScaleAnimation;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.ScrollView;
+import android.widget.TextSwitcher;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.fragment.app.Fragment;
+import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import org.signal.core.util.ThreadUtil;
+import org.signal.core.util.concurrent.SignalExecutors;
+import org.signal.core.util.logging.Log;
+import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
+import org.thoughtcrime.securesms.crypto.ReentrantSessionLock;
+import org.thoughtcrime.securesms.database.IdentityDatabase;
+import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
+import org.thoughtcrime.securesms.jobs.MultiDeviceVerifiedUpdateJob;
+import org.thoughtcrime.securesms.qr.QrCode;
+import org.thoughtcrime.securesms.recipients.LiveRecipient;
+import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.recipients.RecipientId;
+import org.thoughtcrime.securesms.storage.StorageSyncHelper;
+import org.thoughtcrime.securesms.util.FeatureFlags;
+import org.thoughtcrime.securesms.util.IdentityUtil;
+import org.thoughtcrime.securesms.util.Util;
+import org.thoughtcrime.securesms.util.ViewUtil;
+import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
+import org.whispersystems.libsignal.IdentityKey;
+import org.whispersystems.libsignal.fingerprint.Fingerprint;
+import org.whispersystems.libsignal.fingerprint.FingerprintVersionMismatchException;
+import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator;
+import org.whispersystems.signalservice.api.SignalSessionLock;
+
+import java.nio.charset.Charset;
+import java.util.Locale;
+
+/**
+ * Fragment to display a user's identity key.
+ */
+public class VerifyDisplayFragment extends Fragment implements ViewTreeObserver.OnScrollChangedListener {
+
+ private static final String TAG = Log.tag(VerifyDisplayFragment.class);
+
+ private static final String RECIPIENT_ID = "recipient_id";
+ private static final String REMOTE_IDENTITY = "remote_identity";
+ private static final String LOCAL_IDENTITY = "local_identity";
+ private static final String LOCAL_NUMBER = "local_number";
+ private static final String VERIFIED_STATE = "verified_state";
+
+ private LiveRecipient recipient;
+ private IdentityKey localIdentity;
+ private IdentityKey remoteIdentity;
+ private Fingerprint fingerprint;
+
+ private Toolbar toolbar;
+ private ScrollView scrollView;
+ private View numbersContainer;
+ private View loading;
+ private View qrCodeContainer;
+ private ImageView qrCode;
+ private ImageView qrVerified;
+ private TextSwitcher tapLabel;
+ private TextView description;
+ private Callback callback;
+ private Button verifyButton;
+ private View toolbarShadow;
+ private View bottomShadow;
+
+ private TextView[] codes = new TextView[12];
+ private boolean animateSuccessOnDraw = false;
+ private boolean animateFailureOnDraw = false;
+ private boolean currentVerifiedState = false;
+
+ static VerifyDisplayFragment create(@NonNull RecipientId recipientId,
+ @NonNull IdentityKeyParcelable remoteIdentity,
+ @NonNull IdentityKeyParcelable localIdentity,
+ @NonNull String localNumber,
+ boolean verifiedState)
+ {
+ Bundle extras = new Bundle();
+ extras.putParcelable(RECIPIENT_ID, recipientId);
+ extras.putParcelable(REMOTE_IDENTITY, remoteIdentity);
+ extras.putParcelable(LOCAL_IDENTITY, localIdentity);
+ extras.putString(LOCAL_NUMBER, localNumber);
+ extras.putBoolean(VERIFIED_STATE, verifiedState);
+
+ VerifyDisplayFragment fragment = new VerifyDisplayFragment();
+ fragment.setArguments(extras);
+
+ return fragment;
+ }
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+
+ if (context instanceof Callback) {
+ callback = (Callback) context;
+ } else if (getParentFragment() instanceof Callback) {
+ callback = (Callback) getParentFragment();
+ } else {
+ throw new ClassCastException("Cannot find ScanListener in parent component");
+ }
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
+ return ViewUtil.inflate(inflater, viewGroup, R.layout.verify_display_fragment);
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ this.toolbar = view.findViewById(R.id.toolbar);
+ this.scrollView = view.findViewById(R.id.scroll_view);
+ this.numbersContainer = view.findViewById(R.id.number_table);
+ this.loading = view.findViewById(R.id.loading);
+ this.qrCodeContainer = view.findViewById(R.id.qr_code_container);
+ this.qrCode = view.findViewById(R.id.qr_code);
+ this.verifyButton = view.findViewById(R.id.verify_button);
+ this.qrVerified = view.findViewById(R.id.qr_verified);
+ this.description = view.findViewById(R.id.description);
+ this.tapLabel = view.findViewById(R.id.tap_label);
+ this.toolbarShadow = view.findViewById(R.id.toolbar_shadow);
+ this.bottomShadow = view.findViewById(R.id.verify_identity_bottom_shadow);
+ this.codes[0] = view.findViewById(R.id.code_first);
+ this.codes[1] = view.findViewById(R.id.code_second);
+ this.codes[2] = view.findViewById(R.id.code_third);
+ this.codes[3] = view.findViewById(R.id.code_fourth);
+ this.codes[4] = view.findViewById(R.id.code_fifth);
+ this.codes[5] = view.findViewById(R.id.code_sixth);
+ this.codes[6] = view.findViewById(R.id.code_seventh);
+ this.codes[7] = view.findViewById(R.id.code_eighth);
+ this.codes[8] = view.findViewById(R.id.code_ninth);
+ this.codes[9] = view.findViewById(R.id.code_tenth);
+ this.codes[10] = view.findViewById(R.id.code_eleventh);
+ this.codes[11] = view.findViewById(R.id.code_twelth);
+
+ this.qrCodeContainer.setOnClickListener(v -> callback.onQrCodeContainerClicked());
+ this.registerForContextMenu(numbersContainer);
+
+ updateVerifyButton(getArguments().getBoolean(VERIFIED_STATE, false), false);
+ this.verifyButton.setOnClickListener((button -> updateVerifyButton(!currentVerifiedState, true)));
+
+ this.scrollView.getViewTreeObserver().addOnScrollChangedListener(this);
+
+ toolbar.setNavigationOnClickListener(v -> requireActivity().onBackPressed());
+ toolbar.setOnMenuItemClickListener(this::onToolbarOptionsItemSelected);
+ toolbar.setTitle(R.string.AndroidManifest__verify_safety_number);
+ }
+
+ @Override
+ public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
+ super.onViewStateRestored(savedInstanceState);
+ initializeFingerprint();
+ }
+
+ @Override
+ public void onDestroyView() {
+ this.scrollView.getViewTreeObserver().removeOnScrollChangedListener(this);
+ super.onDestroyView();
+ }
+
+ private void initializeFingerprint() {
+ RecipientId recipientId = getArguments().getParcelable(RECIPIENT_ID);
+ IdentityKeyParcelable localIdentityParcelable = getArguments().getParcelable(LOCAL_IDENTITY);
+ IdentityKeyParcelable remoteIdentityParcelable = getArguments().getParcelable(REMOTE_IDENTITY);
+
+ this.localIdentity = localIdentityParcelable.get();
+ this.recipient = Recipient.live(recipientId);
+ this.remoteIdentity = remoteIdentityParcelable.get();
+
+ int version;
+ byte[] localId;
+ byte[] remoteId;
+
+ //noinspection WrongThread
+ Recipient resolved = recipient.resolve();
+
+ if (FeatureFlags.verifyV2() && resolved.getAci().isPresent()) {
+ Log.i(TAG, "Using UUID (version 2).");
+ version = 2;
+ localId = Recipient.self().requireAci().toByteArray();
+ remoteId = resolved.requireAci().toByteArray();
+ } else if (!FeatureFlags.verifyV2() && resolved.getE164().isPresent()) {
+ Log.i(TAG, "Using E164 (version 1).");
+ version = 1;
+ localId = Recipient.self().requireE164().getBytes();
+ remoteId = resolved.requireE164().getBytes();
+ } else {
+ Log.w(TAG, String.format(Locale.ENGLISH, "Could not show proper verification! verifyV2: %s, hasUuid: %s, hasE164: %s", FeatureFlags.verifyV2(), resolved.getAci().isPresent(), resolved.getE164().isPresent()));
+ new MaterialAlertDialogBuilder(requireContext())
+ .setMessage(getString(R.string.VerifyIdentityActivity_you_must_first_exchange_messages_in_order_to_view, resolved.getDisplayName(requireContext())))
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> requireActivity().finish())
+ .setOnDismissListener(dialog -> {
+ requireActivity().finish();
+ dialog.dismiss();
+ })
+ .show();
+ return;
+ }
+
+ this.recipient.observe(this, this::setRecipientText);
+
+ SimpleTask.run(() -> new NumericFingerprintGenerator(5200).createFor(version,
+ localId, localIdentity,
+ remoteId, remoteIdentity),
+ fingerprint -> {
+ if (getActivity() == null) return;
+ VerifyDisplayFragment.this.fingerprint = fingerprint;
+ setFingerprintViews(fingerprint, true);
+ initializeOptionsMenu();
+ });
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ setRecipientText(recipient.get());
+
+ if (fingerprint != null) {
+ setFingerprintViews(fingerprint, false);
+ }
+
+ if (animateSuccessOnDraw) {
+ animateSuccessOnDraw = false;
+ animateVerifiedSuccess();
+ } else if (animateFailureOnDraw) {
+ animateFailureOnDraw = false;
+ animateVerifiedFailure();
+ }
+
+ ThreadUtil.postToMain(this::onScrollChanged);
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View view,
+ ContextMenu.ContextMenuInfo menuInfo)
+ {
+ super.onCreateContextMenu(menu, view, menuInfo);
+
+ if (fingerprint != null) {
+ MenuInflater inflater = getActivity().getMenuInflater();
+ inflater.inflate(R.menu.verify_display_fragment_context_menu, menu);
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ if (fingerprint == null) return super.onContextItemSelected(item);
+
+ switch (item.getItemId()) {
+ case R.id.menu_copy:
+ handleCopyToClipboard(fingerprint, codes.length);
+ return true;
+ case R.id.menu_compare:
+ handleCompareWithClipboard(fingerprint);
+ return true;
+ default:
+ return super.onContextItemSelected(item);
+ }
+ }
+
+ private void initializeOptionsMenu() {
+ if (fingerprint != null) {
+ requireActivity().getMenuInflater().inflate(R.menu.verify_identity, toolbar.getMenu());
+ }
+ }
+
+ public boolean onToolbarOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == R.id.verify_identity__share) {
+ handleShare(fingerprint, codes.length);
+ return true;
+ }
+
+ return false;
+ }
+
+ public void setScannedFingerprint(String scanned) {
+ try {
+ if (fingerprint.getScannableFingerprint().compareTo(scanned.getBytes("ISO-8859-1"))) {
+ this.animateSuccessOnDraw = true;
+ } else {
+ this.animateFailureOnDraw = true;
+ }
+ } catch (FingerprintVersionMismatchException e) {
+ Log.w(TAG, e);
+ if (e.getOurVersion() < e.getTheirVersion()) {
+ Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_your_contact_is_running_a_newer_version_of_Signal, Toast.LENGTH_LONG).show();
+ } else {
+ Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_your_contact_is_running_an_old_version_of_signal, Toast.LENGTH_LONG).show();
+ }
+ this.animateFailureOnDraw = true;
+ } catch (Exception e) {
+ Log.w(TAG, e);
+ Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_the_scanned_qr_code_is_not_a_correctly_formatted_safety_number, Toast.LENGTH_LONG).show();
+ this.animateFailureOnDraw = true;
+ }
+ }
+
+ private @NonNull String getFormattedSafetyNumbers(@NonNull Fingerprint fingerprint, int segmentCount) {
+ String[] segments = getSegments(fingerprint, segmentCount);
+ StringBuilder result = new StringBuilder();
+
+ for (int i = 0; i < segments.length; i++) {
+ result.append(segments[i]);
+
+ if (i != segments.length - 1) {
+ if (((i + 1) % 4) == 0) result.append('\n');
+ else result.append(' ');
+ }
+ }
+
+ return result.toString();
+ }
+
+ private void handleCopyToClipboard(Fingerprint fingerprint, int segmentCount) {
+ Util.writeTextToClipboard(getActivity(), getFormattedSafetyNumbers(fingerprint, segmentCount));
+ }
+
+ private void handleCompareWithClipboard(Fingerprint fingerprint) {
+ String clipboardData = Util.readTextFromClipboard(getActivity());
+
+ if (clipboardData == null) {
+ Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_no_safety_number_to_compare_was_found_in_the_clipboard, Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ String numericClipboardData = clipboardData.replaceAll("\\D", "");
+
+ if (TextUtils.isEmpty(numericClipboardData) || numericClipboardData.length() != 60) {
+ Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_no_safety_number_to_compare_was_found_in_the_clipboard, Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ if (fingerprint.getDisplayableFingerprint().getDisplayText().equals(numericClipboardData)) {
+ animateVerifiedSuccess();
+ } else {
+ animateVerifiedFailure();
+ }
+ }
+
+ private void handleShare(@NonNull Fingerprint fingerprint, int segmentCount) {
+ String shareString =
+ getString(R.string.VerifyIdentityActivity_our_signal_safety_number) + "\n" +
+ getFormattedSafetyNumbers(fingerprint, segmentCount) + "\n";
+
+ Intent intent = new Intent();
+ intent.setAction(Intent.ACTION_SEND);
+ intent.putExtra(Intent.EXTRA_TEXT, shareString);
+ intent.setType("text/plain");
+
+ try {
+ startActivity(Intent.createChooser(intent, getString(R.string.VerifyIdentityActivity_share_safety_number_via)));
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_no_app_to_share_to, Toast.LENGTH_LONG).show();
+ }
+ }
+
+ private void setRecipientText(Recipient recipient) {
+ description.setText(Html.fromHtml(String.format(getActivity().getString(R.string.verify_display_fragment__to_verify_the_security_of_your_end_to_end_encryption_with_s), recipient.getDisplayName(getContext()))));
+ description.setMovementMethod(LinkMovementMethod.getInstance());
+ }
+
+ private void setFingerprintViews(Fingerprint fingerprint, boolean animate) {
+ String[] segments = getSegments(fingerprint, codes.length);
+
+ for (int i = 0; i < codes.length; i++) {
+ if (animate) setCodeSegment(codes[i], segments[i]);
+ else codes[i].setText(segments[i]);
+ }
+
+ byte[] qrCodeData = fingerprint.getScannableFingerprint().getSerialized();
+ String qrCodeString = new String(qrCodeData, Charset.forName("ISO-8859-1"));
+ Bitmap qrCodeBitmap = QrCode.create(qrCodeString);
+
+ qrCode.setImageBitmap(qrCodeBitmap);
+
+ if (animate) {
+ ViewUtil.fadeIn(qrCode, 1000);
+ ViewUtil.fadeIn(tapLabel, 1000);
+ ViewUtil.fadeOut(loading, 300, View.GONE);
+ } else {
+ qrCode.setVisibility(View.VISIBLE);
+ tapLabel.setVisibility(View.VISIBLE);
+ loading.setVisibility(View.GONE);
+ }
+ }
+
+ private void setCodeSegment(final TextView codeView, String segment) {
+ ValueAnimator valueAnimator = new ValueAnimator();
+ valueAnimator.setObjectValues(0, Integer.parseInt(segment));
+
+ valueAnimator.addUpdateListener(animation -> {
+ int value = (int) animation.getAnimatedValue();
+ codeView.setText(String.format(Locale.getDefault(), "%05d", value));
+ });
+
+ valueAnimator.setEvaluator(new TypeEvaluator() {
+ public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
+ return Math.round(startValue + (endValue - startValue) * fraction);
+ }
+ });
+
+ valueAnimator.setDuration(1000);
+ valueAnimator.start();
+ }
+
+ private String[] getSegments(Fingerprint fingerprint, int segmentCount) {
+ String[] segments = new String[segmentCount];
+ String digits = fingerprint.getDisplayableFingerprint().getDisplayText();
+ int partSize = digits.length() / segmentCount;
+
+ for (int i = 0; i < segmentCount; i++) {
+ segments[i] = digits.substring(i * partSize, (i * partSize) + partSize);
+ }
+
+ return segments;
+ }
+
+ private Bitmap createVerifiedBitmap(int width, int height, @DrawableRes int id) {
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ Bitmap check = BitmapFactory.decodeResource(getResources(), id);
+ float offset = (width - check.getWidth()) / 2;
+
+ canvas.drawBitmap(check, offset, offset, null);
+
+ return bitmap;
+ }
+
+ private void animateVerifiedSuccess() {
+ Bitmap qrBitmap = ((BitmapDrawable) qrCode.getDrawable()).getBitmap();
+ Bitmap qrSuccess = createVerifiedBitmap(qrBitmap.getWidth(), qrBitmap.getHeight(), R.drawable.ic_check_white_48dp);
+
+ qrVerified.setImageBitmap(qrSuccess);
+ qrVerified.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.MULTIPLY);
+
+ tapLabel.setText(getString(R.string.verify_display_fragment__successful_match));
+
+ animateVerified();
+ }
+
+ private void animateVerifiedFailure() {
+ Bitmap qrBitmap = ((BitmapDrawable) qrCode.getDrawable()).getBitmap();
+ Bitmap qrSuccess = createVerifiedBitmap(qrBitmap.getWidth(), qrBitmap.getHeight(), R.drawable.ic_close_white_48dp);
+
+ qrVerified.setImageBitmap(qrSuccess);
+ qrVerified.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.MULTIPLY);
+
+ tapLabel.setText(getString(R.string.verify_display_fragment__failed_to_verify_safety_number));
+
+ animateVerified();
+ }
+
+ private void animateVerified() {
+ ScaleAnimation scaleAnimation = new ScaleAnimation(0, 1, 0, 1,
+ ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
+ ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
+ scaleAnimation.setInterpolator(new FastOutSlowInInterpolator());
+ scaleAnimation.setDuration(800);
+ scaleAnimation.setAnimationListener(new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {}
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ qrVerified.postDelayed(() -> {
+ ScaleAnimation scaleAnimation1 = new ScaleAnimation(1, 0, 1, 0,
+ ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
+ ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
+
+ scaleAnimation1.setInterpolator(new AnticipateInterpolator());
+ scaleAnimation1.setDuration(500);
+ ViewUtil.animateOut(qrVerified, scaleAnimation1, View.GONE);
+ ViewUtil.fadeIn(qrCode, 800);
+ qrCodeContainer.setEnabled(true);
+ tapLabel.setText(getString(R.string.verify_display_fragment__tap_to_scan));
+ }, 2000);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {}
+ });
+
+ ViewUtil.fadeOut(qrCode, 200, View.INVISIBLE);
+ ViewUtil.animateIn(qrVerified, scaleAnimation);
+ qrCodeContainer.setEnabled(false);
+ }
+
+ private void updateVerifyButton(boolean verified, boolean update) {
+ currentVerifiedState = verified;
+
+ if (verified) {
+ verifyButton.setText(R.string.verify_display_fragment__clear_verification);
+ } else {
+ verifyButton.setText(R.string.verify_display_fragment__mark_as_verified);
+ }
+
+ if (update) {
+ final RecipientId recipientId = recipient.getId();
+ final Context context = requireContext().getApplicationContext();
+
+ SignalExecutors.BOUNDED.execute(() -> {
+ try (SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) {
+ if (verified) {
+ Log.i(TAG, "Saving identity: " + recipientId);
+ ApplicationDependencies.getIdentityStore()
+ .saveIdentityWithoutSideEffects(recipientId,
+ remoteIdentity,
+ IdentityDatabase.VerifiedStatus.VERIFIED,
+ false,
+ System.currentTimeMillis(),
+ true);
+ } else {
+ ApplicationDependencies.getIdentityStore().setVerified(recipientId, remoteIdentity, IdentityDatabase.VerifiedStatus.DEFAULT);
+ }
+
+ ApplicationDependencies.getJobManager()
+ .add(new MultiDeviceVerifiedUpdateJob(recipientId,
+ remoteIdentity,
+ verified ? IdentityDatabase.VerifiedStatus.VERIFIED
+ : IdentityDatabase.VerifiedStatus.DEFAULT));
+ StorageSyncHelper.scheduleSyncForDataChange();
+
+ IdentityUtil.markIdentityVerified(context, recipient.get(), verified, false);
+ }
+ });
+ }
+ }
+
+
+ @Override public void onScrollChanged() {
+ if (scrollView.canScrollVertically(-1)) {
+ if (toolbarShadow.getVisibility() != View.VISIBLE) {
+ ViewUtil.fadeIn(toolbarShadow, 250);
+ }
+ } else {
+ if (toolbarShadow.getVisibility() != View.GONE) {
+ ViewUtil.fadeOut(toolbarShadow, 250);
+ }
+ }
+
+ if (scrollView.canScrollVertically(1)) {
+ if (bottomShadow.getVisibility() != View.VISIBLE) {
+ ViewUtil.fadeIn(bottomShadow, 250);
+ }
+ } else {
+ ViewUtil.fadeOut(bottomShadow, 250);
+ }
+ }
+
+ interface Callback {
+ void onQrCodeContainerClicked();
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyIdentityActivity.java b/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyIdentityActivity.java
new file mode 100644
index 0000000000..832b1c1756
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyIdentityActivity.java
@@ -0,0 +1,85 @@
+package org.thoughtcrime.securesms.verify;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+
+import org.thoughtcrime.securesms.PassphraseRequiredActivity;
+import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
+import org.thoughtcrime.securesms.database.IdentityDatabase;
+import org.thoughtcrime.securesms.database.model.IdentityRecord;
+import org.thoughtcrime.securesms.recipients.RecipientId;
+import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
+import org.thoughtcrime.securesms.util.DynamicTheme;
+import org.whispersystems.libsignal.IdentityKey;
+
+/**
+ * Activity for verifying identity keys.
+ *
+ * @author Moxie Marlinspike
+ */
+public class VerifyIdentityActivity extends PassphraseRequiredActivity {
+
+ private static final String RECIPIENT_EXTRA = "recipient_id";
+ private static final String IDENTITY_EXTRA = "recipient_identity";
+ private static final String VERIFIED_EXTRA = "verified_state";
+
+ private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
+
+ public static Intent newIntent(@NonNull Context context,
+ @NonNull IdentityRecord identityRecord)
+ {
+ return newIntent(context,
+ identityRecord.getRecipientId(),
+ identityRecord.getIdentityKey(),
+ identityRecord.getVerifiedStatus() == IdentityDatabase.VerifiedStatus.VERIFIED);
+ }
+
+ public static Intent newIntent(@NonNull Context context,
+ @NonNull IdentityRecord identityRecord,
+ boolean verified)
+ {
+ return newIntent(context,
+ identityRecord.getRecipientId(),
+ identityRecord.getIdentityKey(),
+ verified);
+ }
+
+ public static Intent newIntent(@NonNull Context context,
+ @NonNull RecipientId recipientId,
+ @NonNull IdentityKey identityKey,
+ boolean verified)
+ {
+ Intent intent = new Intent(context, VerifyIdentityActivity.class);
+
+ intent.putExtra(RECIPIENT_EXTRA, recipientId);
+ intent.putExtra(IDENTITY_EXTRA, new IdentityKeyParcelable(identityKey));
+ intent.putExtra(VERIFIED_EXTRA, verified);
+
+ return intent;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState, boolean ready) {
+ super.onCreate(savedInstanceState, ready);
+ dynamicTheme.onCreate(this);
+
+ VerifyIdentityFragment fragment = VerifyIdentityFragment.create(
+ getIntent().getParcelableExtra(RECIPIENT_EXTRA),
+ getIntent().getParcelableExtra(IDENTITY_EXTRA),
+ getIntent().getBooleanExtra(VERIFIED_EXTRA, false)
+ );
+
+ getSupportFragmentManager().beginTransaction()
+ .replace(android.R.id.content, fragment)
+ .commitAllowingStateLoss();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ dynamicTheme.onResume(this);
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyIdentityFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyIdentityFragment.kt
new file mode 100644
index 0000000000..daa1920c5b
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyIdentityFragment.kt
@@ -0,0 +1,98 @@
+package org.thoughtcrime.securesms.verify
+
+import android.Manifest
+import android.os.Bundle
+import android.view.View
+import android.widget.Toast
+import androidx.fragment.app.Fragment
+import org.signal.core.util.ThreadUtil
+import org.thoughtcrime.securesms.R
+import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable
+import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
+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
+
+/**
+ * Fragment to assist user in verifying recipient identity utilizing keys.
+ */
+class VerifyIdentityFragment : Fragment(R.layout.fragment_container), ScanListener, VerifyDisplayFragment.Callback {
+
+ companion object {
+ private const val EXTRA_RECIPIENT = "extra.recipient.id"
+ private const val EXTRA_IDENTITY = "extra.recipient.identity"
+ private const val EXTRA_VERIFIED = "extra.verified.state"
+
+ @JvmStatic
+ fun create(
+ recipientId: RecipientId,
+ remoteIdentity: IdentityKeyParcelable,
+ verified: Boolean
+ ): VerifyIdentityFragment {
+ return VerifyIdentityFragment().apply {
+ arguments = Bundle().apply {
+ putParcelable(EXTRA_RECIPIENT, recipientId)
+ putParcelable(EXTRA_IDENTITY, remoteIdentity)
+ putBoolean(EXTRA_VERIFIED, verified)
+ }
+ }
+ }
+ }
+
+ private val displayFragment by lazy {
+ VerifyDisplayFragment.create(
+ recipientId,
+ remoteIdentity,
+ IdentityKeyParcelable(IdentityKeyUtil.getIdentityKey(requireContext())),
+ Recipient.self().requireE164(),
+ isVerified
+ )
+ }
+
+ private val scanFragment = VerifyScanFragment()
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ childFragmentManager.beginTransaction()
+ .replace(R.id.fragment_container, displayFragment)
+ .commitAllowingStateLoss()
+ }
+
+ private val recipientId: RecipientId
+ get() = requireArguments().getParcelable(EXTRA_RECIPIENT)!!
+
+ private val remoteIdentity: IdentityKeyParcelable
+ get() = requireArguments().getParcelable(EXTRA_IDENTITY)!!
+
+ private val isVerified: Boolean
+ get() = requireArguments().getBoolean(EXTRA_VERIFIED)
+
+ override fun onQrDataFound(data: String) {
+ ThreadUtil.runOnMain {
+ ServiceUtil.getVibrator(context).vibrate(50)
+ childFragmentManager.popBackStack()
+ displayFragment.setScannedFingerprint(data)
+ }
+ }
+
+ override fun onQrCodeContainerClicked() {
+ Permissions.with(this)
+ .request(Manifest.permission.CAMERA)
+ .ifNecessary()
+ .withPermanentDenialDialog(getString(R.string.VerifyIdentityActivity_signal_needs_the_camera_permission_in_order_to_scan_a_qr_code_but_it_has_been_permanently_denied))
+ .onAllGranted {
+ childFragmentManager.beginTransaction()
+ .setCustomAnimations(R.anim.slide_from_top, R.anim.slide_to_bottom, R.anim.slide_from_bottom, R.anim.slide_to_top)
+ .replace(R.id.fragment_container, scanFragment)
+ .addToBackStack(null)
+ .commitAllowingStateLoss()
+ }
+ .onAnyDenied { Toast.makeText(requireContext(), R.string.VerifyIdentityActivity_unable_to_scan_qr_code_without_camera_permission, Toast.LENGTH_LONG).show() }
+ .execute()
+ }
+
+ override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
+ Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults)
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyScanFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyScanFragment.kt
new file mode 100644
index 0000000000..9c70dbd461
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyScanFragment.kt
@@ -0,0 +1,72 @@
+package org.thoughtcrime.securesms.verify
+
+import android.content.Context
+import android.content.res.Configuration
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import androidx.core.view.OneShotPreDrawListener
+import androidx.fragment.app.Fragment
+import org.thoughtcrime.securesms.R
+import org.thoughtcrime.securesms.components.ShapeScrim
+import org.thoughtcrime.securesms.components.camera.CameraView
+import org.thoughtcrime.securesms.keyboard.findListener
+import org.thoughtcrime.securesms.qr.ScanListener
+import org.thoughtcrime.securesms.qr.ScanningThread
+import org.thoughtcrime.securesms.util.ViewUtil
+
+/**
+ * QR Scanner for identity verification
+ */
+class VerifyScanFragment : Fragment() {
+ private lateinit var cameraView: CameraView
+ private lateinit var cameraScrim: ShapeScrim
+ private lateinit var cameraMarks: ImageView
+ private lateinit var scanningThread: ScanningThread
+ private lateinit var scanListener: ScanListener
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ scanListener = findListener()!!
+ }
+
+ override fun onCreateView(inflater: LayoutInflater, viewGroup: ViewGroup?, bundle: Bundle?): View? {
+ return ViewUtil.inflate(inflater, viewGroup!!, R.layout.verify_scan_fragment)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ cameraView = view.findViewById(R.id.scanner)
+ cameraScrim = view.findViewById(R.id.camera_scrim)
+ cameraMarks = view.findViewById(R.id.camera_marks)
+ OneShotPreDrawListener.add(cameraScrim) {
+ val width = cameraScrim.scrimWidth
+ val height = cameraScrim.scrimHeight
+ ViewUtil.updateLayoutParams(cameraMarks, width, height)
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ scanningThread = ScanningThread()
+ scanningThread.setScanListener(scanListener)
+ scanningThread.setCharacterSet("ISO-8859-1")
+ cameraView.onResume()
+ cameraView.setPreviewCallback(scanningThread)
+ scanningThread.start()
+ }
+
+ override fun onPause() {
+ super.onPause()
+ cameraView.onPause()
+ scanningThread.stopScanning()
+ }
+
+ override fun onConfigurationChanged(newConfiguration: Configuration) {
+ super.onConfigurationChanged(newConfiguration)
+ cameraView.onPause()
+ cameraView.onResume()
+ cameraView.setPreviewCallback(scanningThread)
+ }
+}