Enable lock screen fallback when biometric authentications may not work.

Fixes #9407
Fixes #10166
This commit is contained in:
Fumiaki Yoshimatsu
2020-10-25 18:22:28 -04:00
committed by Cody Henthorne
parent be4b687e48
commit 3c4252a933
17 changed files with 99 additions and 104 deletions

View File

@@ -17,7 +17,6 @@
package org.thoughtcrime.securesms;
import android.animation.Animator;
import android.annotation.SuppressLint;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
@@ -46,9 +45,11 @@ import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar;
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
import androidx.core.os.CancellationSignal;
import androidx.biometric.BiometricManager;
import androidx.biometric.BiometricManager.Authenticators;
import androidx.biometric.BiometricPrompt;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
@@ -70,7 +71,10 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
*/
public class PassphrasePromptActivity extends PassphraseActivity {
private static final String TAG = PassphrasePromptActivity.class.getSimpleName();
private static final String TAG = Log.tag(PassphrasePromptActivity.class);
private static final int BIOMETRIC_AUTHENTICATORS = Authenticators.BIOMETRIC_STRONG | Authenticators.BIOMETRIC_WEAK;
private static final int ALLOWED_AUTHENTICATORS = BIOMETRIC_AUTHENTICATORS | Authenticators.DEVICE_CREDENTIAL;
private static final short AUTHENTICATE_REQUEST_CODE = 1007;
private DynamicIntroTheme dynamicTheme = new DynamicIntroTheme();
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
@@ -84,12 +88,12 @@ public class PassphrasePromptActivity extends PassphraseActivity {
private ImageButton hideButton;
private AnimatingToggle visibilityToggle;
private FingerprintManagerCompat fingerprintManager;
private CancellationSignal fingerprintCancellationSignal;
private FingerprintListener fingerprintListener;
private BiometricManager biometricManager;
private BiometricPrompt biometricPrompt;
private BiometricPrompt.PromptInfo biometricPromptInfo;
private boolean authenticated;
private boolean failure;
private boolean hadFailure;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -112,20 +116,11 @@ public class PassphrasePromptActivity extends PassphraseActivity {
setLockTypeVisibility();
if (TextSecurePreferences.isScreenLockEnabled(this) && !authenticated && !failure) {
if (TextSecurePreferences.isScreenLockEnabled(this) && !authenticated && !hadFailure) {
resumeScreenLock();
}
failure = false;
}
@Override
public void onPause() {
super.onPause();
if (TextSecurePreferences.isScreenLockEnabled(this)) {
pauseScreenLock();
}
hadFailure = false;
}
@Override
@@ -160,15 +155,16 @@ public class PassphrasePromptActivity extends PassphraseActivity {
}
@Override
@SuppressLint("MissingSuperCall") // no fragments to dispatch to
public void onActivityResult(int requestCode, int resultcode, Intent data) {
if (requestCode != 1) return;
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultcode == RESULT_OK) {
if (requestCode != AUTHENTICATE_REQUEST_CODE) return;
if (resultCode == RESULT_OK) {
handleAuthenticated();
} else {
Log.w(TAG, "Authentication failed");
failure = true;
hadFailure = true;
}
}
@@ -219,16 +215,20 @@ public class PassphrasePromptActivity extends PassphraseActivity {
ImageButton okButton = findViewById(R.id.ok_button);
Toolbar toolbar = findViewById(R.id.toolbar);
showButton = findViewById(R.id.passphrase_visibility);
hideButton = findViewById(R.id.passphrase_visibility_off);
visibilityToggle = findViewById(R.id.button_toggle);
passphraseText = findViewById(R.id.passphrase_edit);
passphraseAuthContainer = findViewById(R.id.password_auth_container);
fingerprintPrompt = findViewById(R.id.fingerprint_auth_container);
lockScreenButton = findViewById(R.id.lock_screen_auth_container);
fingerprintManager = FingerprintManagerCompat.from(this);
fingerprintCancellationSignal = new CancellationSignal();
fingerprintListener = new FingerprintListener();
showButton = findViewById(R.id.passphrase_visibility);
hideButton = findViewById(R.id.passphrase_visibility_off);
visibilityToggle = findViewById(R.id.button_toggle);
passphraseText = findViewById(R.id.passphrase_edit);
passphraseAuthContainer = findViewById(R.id.password_auth_container);
fingerprintPrompt = findViewById(R.id.fingerprint_auth_container);
lockScreenButton = findViewById(R.id.lock_screen_auth_container);
biometricManager = BiometricManager.from(this);
biometricPrompt = new BiometricPrompt(this, new BiometricAuthenticationListener());
biometricPromptInfo = new BiometricPrompt.PromptInfo
.Builder()
.setAllowedAuthenticators(ALLOWED_AUTHENTICATORS)
.setTitle(getString(R.string.PassphrasePromptActivity_unlock_signal))
.build();
setSupportActionBar(toolbar);
getSupportActionBar().setTitle("");
@@ -254,14 +254,9 @@ public class PassphrasePromptActivity extends PassphraseActivity {
private void setLockTypeVisibility() {
if (TextSecurePreferences.isScreenLockEnabled(this)) {
passphraseAuthContainer.setVisibility(View.GONE);
if (fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints()) {
fingerprintPrompt.setVisibility(View.VISIBLE);
lockScreenButton.setVisibility(View.GONE);
} else {
fingerprintPrompt.setVisibility(View.GONE);
lockScreenButton.setVisibility(View.VISIBLE);
}
fingerprintPrompt.setVisibility(biometricManager.canAuthenticate(BIOMETRIC_AUTHENTICATORS) == BiometricManager.BIOMETRIC_SUCCESS ? View.VISIBLE
: View.GONE);
lockScreenButton.setVisibility(View.VISIBLE);
} else {
passphraseAuthContainer.setVisibility(View.VISIBLE);
fingerprintPrompt.setVisibility(View.GONE);
@@ -280,26 +275,19 @@ public class PassphrasePromptActivity extends PassphraseActivity {
return;
}
if (fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints()) {
Log.i(TAG, "Listening for fingerprints...");
fingerprintCancellationSignal = new CancellationSignal();
fingerprintManager.authenticate(null, 0, fingerprintCancellationSignal, fingerprintListener, null);
if (biometricManager.canAuthenticate(ALLOWED_AUTHENTICATORS) == BiometricManager.BIOMETRIC_SUCCESS) {
Log.i(TAG, "Listening for biometric authentication...");
biometricPrompt.authenticate(biometricPromptInfo);
} else if (Build.VERSION.SDK_INT >= 21){
Log.i(TAG, "firing intent...");
Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(getString(R.string.PassphrasePromptActivity_unlock_signal), "");
startActivityForResult(intent, 1);
startActivityForResult(intent, AUTHENTICATE_REQUEST_CODE);
} else {
Log.w(TAG, "Not compatible...");
handleAuthenticated();
}
}
private void pauseScreenLock() {
if (fingerprintCancellationSignal != null) {
fingerprintCancellationSignal.cancel();
}
}
private void sendEmailToSupport() {
String body = SupportEmailUtil.generateSupportEmailBody(this,
R.string.PassphrasePromptActivity_signal_android_lock_screen,
@@ -359,15 +347,19 @@ public class PassphrasePromptActivity extends PassphraseActivity {
System.gc();
}
private class FingerprintListener extends FingerprintManagerCompat.AuthenticationCallback {
private class BiometricAuthenticationListener extends BiometricPrompt.AuthenticationCallback {
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
Log.w(TAG, "Authentication error: " + errMsgId + " " + errString);
onAuthenticationFailed();
public void onAuthenticationError(int errorCode, @NonNull CharSequence errorString) {
Log.w(TAG, "Authentication error: " + errorCode);
hadFailure = true;
if (errorCode != BiometricPrompt.ERROR_CANCELED && errorCode != BiometricPrompt.ERROR_USER_CANCELED) {
onAuthenticationFailed();
}
}
@Override
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
Log.i(TAG, "onAuthenticationSucceeded");
fingerprintPrompt.setImageResource(R.drawable.ic_check_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.SRC_IN);
@@ -384,8 +376,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
@Override
public void onAuthenticationFailed() {
Log.w(TAG, "onAuthenticatoinFailed()");
FingerprintManagerCompat.AuthenticationCallback callback = this;
Log.w(TAG, "onAuthenticationFailed()");
fingerprintPrompt.setImageResource(R.drawable.ic_close_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.SRC_IN);
@@ -409,6 +400,5 @@ public class PassphrasePromptActivity extends PassphraseActivity {
fingerprintPrompt.startAnimation(shake);
}
}
}

View File

@@ -301,6 +301,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement
byte[] localId;
byte[] remoteId;
//noinspection WrongThread
Recipient resolved = recipient.resolve();
if (FeatureFlags.verifyV2() && resolved.getUuid().isPresent()) {

View File

@@ -260,16 +260,16 @@ public class ConversationFragment extends LoggingFragment {
this.messageCountsViewModel = ViewModelProviders.of(requireActivity()).get(MessageCountsViewModel.class);
this.conversationViewModel = ViewModelProviders.of(requireActivity(), new ConversationViewModel.Factory()).get(ConversationViewModel.class);
conversationViewModel.getMessages().observe(this, messages -> {
conversationViewModel.getMessages().observe(getViewLifecycleOwner(), messages -> {
ConversationAdapter adapter = getListAdapter();
if (adapter != null) {
getListAdapter().submitList(messages);
}
});
conversationViewModel.getConversationMetadata().observe(this, this::presentConversationMetadata);
conversationViewModel.getConversationMetadata().observe(getViewLifecycleOwner(), this::presentConversationMetadata);
conversationViewModel.getShowMentionsButton().observe(this, shouldShow -> {
conversationViewModel.getShowMentionsButton().observe(getViewLifecycleOwner(), shouldShow -> {
if (shouldShow) {
ViewUtil.animateIn(scrollToMentionButton, mentionButtonInAnimation);
} else {
@@ -277,7 +277,7 @@ public class ConversationFragment extends LoggingFragment {
}
});
conversationViewModel.getShowScrollToBottom().observe(this, shouldShow -> {
conversationViewModel.getShowScrollToBottom().observe(getViewLifecycleOwner(), shouldShow -> {
if (shouldShow) {
ViewUtil.animateIn(scrollToBottomButton, scrollButtonInAnimation);
} else {
@@ -367,7 +367,7 @@ public class ConversationFragment extends LoggingFragment {
@Override
public void onStop() {
super.onStop();
ApplicationDependencies.getTypingStatusRepository().getTypists(threadId).removeObservers(this);
ApplicationDependencies.getTypingStatusRepository().getTypists(threadId).removeObservers(getViewLifecycleOwner());
}
@Override
@@ -542,7 +542,7 @@ public class ConversationFragment extends LoggingFragment {
list.addOnScrollListener(new ShadowScrollListener());
if (oldThreadId != threadId) {
ApplicationDependencies.getTypingStatusRepository().getTypists(oldThreadId).removeObservers(this);
ApplicationDependencies.getTypingStatusRepository().getTypists(oldThreadId).removeObservers(getViewLifecycleOwner());
}
}
@@ -580,8 +580,8 @@ public class ConversationFragment extends LoggingFragment {
LiveData<TypingStatusRepository.TypingState> typists = ApplicationDependencies.getTypingStatusRepository().getTypists(threadId);
typists.removeObservers(this);
typists.observe(this, typingState -> {
typists.removeObservers(getViewLifecycleOwner());
typists.observe(getViewLifecycleOwner(), typingState -> {
List<Recipient> recipients;
boolean replacedByIncomingMessage;

View File

@@ -532,7 +532,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
}
private void initializeTypingObserver() {
ApplicationDependencies.getTypingStatusRepository().getTypingThreads().observe(this, threadIds -> {
ApplicationDependencies.getTypingStatusRepository().getTypingThreads().observe(getViewLifecycleOwner(), threadIds -> {
if (threadIds == null) {
threadIds = Collections.emptySet();
}

View File

@@ -127,7 +127,7 @@ public class HelpFragment extends LoggingFragment {
setSpinning(next);
problem.setEnabled(false);
helpViewModel.onSubmitClicked(includeDebugLogs.isChecked()).observe(this, result -> {
helpViewModel.onSubmitClicked(includeDebugLogs.isChecked()).observe(getViewLifecycleOwner(), result -> {
if (result.getDebugLogUrl().isPresent()) {
submitFormWithDebugLog(result.getDebugLogUrl().get());
} else if (result.isError()) {

View File

@@ -127,7 +127,7 @@ public final class InsightsDashboardDialogFragment extends DialogFragment {
viewModel = ViewModelProviders.of(this, factory).get(InsightsDashboardViewModel.class);
viewModel.getState().observe(this, state -> {
viewModel.getState().observe(getViewLifecycleOwner(), state -> {
updateInsecurePercent(state.getData());
updateInsecureRecipients(state.getInsecureRecipients());
updateUserAvatar(state.getUserAvatar());

View File

@@ -79,7 +79,7 @@ public final class InsightsModalDialogFragment extends DialogFragment {
final InsightsModalViewModel.Factory factory = new InsightsModalViewModel.Factory(repository);
final InsightsModalViewModel viewModel = ViewModelProviders.of(this, factory).get(InsightsModalViewModel.class);
viewModel.getState().observe(this, state -> {
viewModel.getState().observe(getViewLifecycleOwner(), state -> {
updateInsecurePercent(state.getData());
updateUserAvatar(state.getUserAvatar());
});

View File

@@ -128,7 +128,7 @@ public final class MediaOverviewPageFragment extends Fragment
MediaOverviewViewModel viewModel = MediaOverviewViewModel.getMediaOverviewViewModel(requireActivity());
viewModel.getSortOrder()
.observe(this, sorting -> {
.observe(getViewLifecycleOwner(), sorting -> {
if (sorting != null) {
this.sorting = sorting;
adapter.setShowFileSizes(sorting.isRelatedToFileSize());
@@ -139,7 +139,7 @@ public final class MediaOverviewPageFragment extends Fragment
if (gridMode == GridMode.FOLLOW_MODEL) {
viewModel.getDetailLayout()
.observe(this, this::setDetailView);
.observe(getViewLifecycleOwner(), this::setDetailView);
} else {
setDetailView(gridMode == GridMode.FIXED_DETAIL);
}

View File

@@ -111,8 +111,8 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment,
GestureDetector gestureDetector = new GestureDetector(flipGestureListener);
cameraPreview.setOnTouchListener((v, event) -> gestureDetector.onTouchEvent(event));
viewModel.getMostRecentMediaItem().observe(this, this::presentRecentItemThumbnail);
viewModel.getHudState().observe(this, this::presentHud);
viewModel.getMostRecentMediaItem().observe(getViewLifecycleOwner(), this::presentRecentItemThumbnail);
viewModel.getHudState().observe(getViewLifecycleOwner(), this::presentHud);
}
@Override

View File

@@ -122,8 +122,8 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
onOrientationChanged(getResources().getConfiguration().orientation);
viewModel.getMostRecentMediaItem().observe(this, this::presentRecentItemThumbnail);
viewModel.getHudState().observe(this, this::presentHud);
viewModel.getMostRecentMediaItem().observe(getViewLifecycleOwner(), this::presentRecentItemThumbnail);
viewModel.getHudState().observe(getViewLifecycleOwner(), this::presentHud);
}
@Override

View File

@@ -104,7 +104,7 @@ public class MediaPickerFolderFragment extends Fragment implements MediaPickerFo
list.setLayoutManager(layoutManager);
list.setAdapter(adapter);
viewModel.getFolders(requireContext()).observe(this, adapter::setFolders);
viewModel.getFolders(requireContext()).observe(getViewLifecycleOwner(), adapter::setFolders);
initToolbar(view.findViewById(R.id.mediapicker_toolbar));
}

View File

@@ -110,8 +110,8 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem
initToolbar(view.findViewById(R.id.mediapicker_toolbar));
onScreenWidthChanged(getScreenWidth());
viewModel.getSelectedMedia().observe(this, adapter::setSelected);
viewModel.getMediaInBucket(requireContext(), bucketId).observe(this, adapter::setMedia);
viewModel.getSelectedMedia().observe(getViewLifecycleOwner(), adapter::setSelected);
viewModel.getMediaInBucket(requireContext(), bucketId).observe(getViewLifecycleOwner(), adapter::setMedia);
}
@Override

View File

@@ -115,7 +115,7 @@ public class MediaSendFragment extends Fragment {
private void initViewModel() {
viewModel = ViewModelProviders.of(requireActivity(), new MediaSendViewModel.Factory(requireActivity().getApplication(), new MediaRepository())).get(MediaSendViewModel.class);
viewModel.getSelectedMedia().observe(this, media -> {
viewModel.getSelectedMedia().observe(getViewLifecycleOwner(), media -> {
if (Util.isEmpty(media)) {
return;
}
@@ -123,7 +123,7 @@ public class MediaSendFragment extends Fragment {
fragmentPagerAdapter.setMedia(media);
});
viewModel.getPosition().observe(this, position -> {
viewModel.getPosition().observe(getViewLifecycleOwner(), position -> {
if (position == null || position < 0) return;
fragmentPager.setCurrentItem(position, true);

View File

@@ -110,8 +110,8 @@ public class PinRestoreEntryFragment extends LoggingFragment {
private void initViewModel() {
viewModel = ViewModelProviders.of(this).get(PinRestoreViewModel.class);
viewModel.getTriesRemaining().observe(this, this::presentTriesRemaining);
viewModel.getEvent().observe(this, this::presentEvent);
viewModel.getTriesRemaining().observe(getViewLifecycleOwner(), this::presentTriesRemaining);
viewModel.getEvent().observe(getViewLifecycleOwner(), this::presentEvent);
}
private void presentTriesRemaining(PinRestoreViewModel.TriesRemaining triesRemaining) {

View File

@@ -97,7 +97,7 @@ public final class EnterCodeFragment extends BaseRegistrationFragment
noCodeReceivedHelp.setOnClickListener(v -> sendEmailToSupport());
RegistrationViewModel model = getModel();
model.getSuccessfulCodeRequestAttempts().observe(this, (attempts) -> {
model.getSuccessfulCodeRequestAttempts().observe(getViewLifecycleOwner(), (attempts) -> {
if (attempts >= 3) {
noCodeReceivedHelp.setVisibility(View.VISIBLE);
scrollView.postDelayed(() -> scrollView.smoothScrollTo(0, noCodeReceivedHelp.getBottom()), 15000);
@@ -328,9 +328,9 @@ public final class EnterCodeFragment extends BaseRegistrationFragment
super.onResume();
RegistrationViewModel model = getModel();
model.getLiveNumber().observe(this, (s) -> header.setText(requireContext().getString(R.string.RegistrationActivity_enter_the_code_we_sent_to_s, s.getFullFormattedNumber())));
model.getLiveNumber().observe(getViewLifecycleOwner(), (s) -> header.setText(requireContext().getString(R.string.RegistrationActivity_enter_the_code_we_sent_to_s, s.getFullFormattedNumber())));
model.getCanCallAtTime().observe(this, callAtTime -> callMeCountDown.startCountDownTo(callAtTime));
model.getCanCallAtTime().observe(getViewLifecycleOwner(), callAtTime -> callMeCountDown.startCountDownTo(callAtTime));
}
private void sendEmailToSupport() {