Enable lock screen fallback when biometric authentications may not work.

Fixes #9407
Fixes #10166
fork-5.53.8
Fumiaki Yoshimatsu 2020-10-25 18:22:28 -04:00 zatwierdzone przez Cody Henthorne
rodzic be4b687e48
commit 3c4252a933
17 zmienionych plików z 99 dodań i 104 usunięć

Wyświetl plik

@ -312,6 +312,7 @@ dependencies {
implementation "androidx.camera:camera-view:1.0.0-alpha18"
implementation "androidx.concurrent:concurrent-futures:1.0.0"
implementation "androidx.autofill:autofill:1.0.0"
implementation "androidx.biometric:biometric:1.1.0"
implementation ('com.google.firebase:firebase-messaging:20.2.0') {
exclude group: 'com.google.firebase', module: 'firebase-core'

Wyświetl plik

@ -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);
}
}
}

Wyświetl plik

@ -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()) {

Wyświetl plik

@ -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;

Wyświetl plik

@ -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();
}

Wyświetl plik

@ -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()) {

Wyświetl plik

@ -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());

Wyświetl plik

@ -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());
});

Wyświetl plik

@ -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);
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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));
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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);

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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() {

Wyświetl plik

@ -3,8 +3,8 @@
dependencyVerification {
verify = [
['androidx.activity:activity:1.0.0',
'd1bc9842455c2e534415d88c44df4d52413b478db9093a1ba36324f705f44c3d'],
['androidx.activity:activity:1.1.0',
'4f2b35916768032f7d0c20e250e28b29037ed4ce9ebf3da4fcd51bcb0c6067ef'],
['androidx.annotation:annotation-experimental:1.0.0',
'b219d2b568e7e4ba534e09f8c2fd242343df6ccbdfbbe938846f5d740e6b0b11'],
@ -30,6 +30,9 @@ dependencyVerification {
['androidx.autofill:autofill:1.0.0',
'c9468f56e05006ea151a426c54957cd0799b8b83a579d2846dd22061f33e5ecd'],
['androidx.biometric:biometric:1.1.0',
'270c7b7d99942d5ec1dd88594e4648feb33d8e31d8c3c2ab2321d49d9abdfc1f'],
['androidx.camera:camera-camera2:1.0.0-beta11',
'54d7c975ea7387f0d7c65faf531005fc543fe8e8d826eb696bb2c7f950041a9c'],
@ -60,8 +63,8 @@ dependencyVerification {
['androidx.coordinatorlayout:coordinatorlayout:1.1.0',
'44a9e30abf56af1025c52a0af506fee9c4131aa55efda52f9fd9451211c5e8cb'],
['androidx.core:core:1.3.1',
'e92ea65a37d589943d405a6a54d1be9d12a225948f26c4e41e511dd55e81efb6'],
['androidx.core:core:1.3.2',
'94de196cd67950cff6ef3e1ac59015f8eaaf61840bdc238f2cf54ddef8dd0be9'],
['androidx.cursoradapter:cursoradapter:1.0.0',
'a81c8fe78815fa47df5b749deb52727ad11f9397da58b16017f4eb2c11e28564'],
@ -78,8 +81,8 @@ dependencyVerification {
['androidx.exifinterface:exifinterface:1.0.0',
'ee48be10aab8f54efff4c14b77d11e10b9eeee4379d5ef6bf297a2923c55cc11'],
['androidx.fragment:fragment:1.1.0',
'a14c8b8f2153f128e800fbd266a6beab1c283982a29ec570d2cc05d307d81496'],
['androidx.fragment:fragment:1.2.5',
'd19e82d142def6c4e136da70bf92f194c0ecc61d14ab4e84567b2ced0920fa93'],
['androidx.gridlayout:gridlayout:1.0.0',
'a7e5dc6f39dbc3dc6ac6d57b02a9c6fd792e80f0e45ddb3bb08e8f03d23c8755'],
@ -105,14 +108,14 @@ dependencyVerification {
['androidx.lifecycle:lifecycle-common-java8:2.1.0',
'a1ec63c1bb973443cb731d78ec336c5e20e7ee35c89cbb32d36f92c55bb02542'],
['androidx.lifecycle:lifecycle-common:2.2.0-alpha05',
['androidx.lifecycle:lifecycle-common:2.2.0',
'63898dabf7cfe5ec5d7ed8b8c2564c1427be876e1496ead95c2703cf59d3734b'],
['androidx.lifecycle:lifecycle-extensions:2.1.0',
'bd53c64b038585215b4959c1a388437a3ad525608a31c58e4283c3e371727d4d'],
['androidx.lifecycle:lifecycle-livedata-core:2.2.0-alpha05',
'6df2bcbf3be50a5fa29e9aa09d39437f2d61d17c2c46ef618d65bbac4a4a99fc'],
['androidx.lifecycle:lifecycle-livedata-core:2.2.0',
'556c1f3af90aa9d7d0d330565adbf6da71b2429148bac91e07c485f4f9abf614'],
['androidx.lifecycle:lifecycle-livedata:2.1.0',
'242e446bed3db36f0df0aab0cb7f91060bd2dab7adcad1117adf54e724cd1d26'],
@ -120,17 +123,17 @@ dependencyVerification {
['androidx.lifecycle:lifecycle-process:2.1.0',
'8cddd0c7f4927bbf71fb71fca000786df82cc597c99463d6916ccbe4a205a9ac'],
['androidx.lifecycle:lifecycle-runtime:2.1.0',
'e5173897b965e870651e83d9d5af1742d3f532d58863223a390ce3a194c8312b'],
['androidx.lifecycle:lifecycle-runtime:2.2.0',
'2f866c07a1f33a8c9bb69a9545d4f20b4f0628cd0a155432386d7cb081e1e0bc'],
['androidx.lifecycle:lifecycle-service:2.1.0',
'23516745f34f16ff7850bb1eadd55cf193dd789cba428de4bca120433e3bfd69'],
['androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-alpha05',
'f503b53f50c4e6c1f9a3d698c4733df6e7a44049fe477ad0b85cc2f460401fbc'],
['androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0',
'3ce866fb822b20fe2f188f974992869a0a6233fe40acbefcff090d6def5e7f33'],
['androidx.lifecycle:lifecycle-viewmodel:2.2.0-alpha05',
'7725715491963440ee483e46526cd4f83af1c758e072e97b3eab2115c6f4db35'],
['androidx.lifecycle:lifecycle-viewmodel:2.2.0',
'967efab24d6c49dd414a8c0ac4a1cd09b018f0b8bb43b739ad360c4158ebde27'],
['androidx.loader:loader:1.0.0',
'11f735cb3b55c458d470bed9e25254375b518b4b1bad6926783a7026db0f5025'],