mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-07-21 04:54:41 +01:00
Add button to help enter AEP from password manager in regV5.
This commit is contained in:
committed by
Michelle Tang
parent
f3b7368043
commit
ebd64305bb
@@ -122,7 +122,7 @@ import org.thoughtcrime.securesms.util.BatterySnapshotTracker;
|
||||
import org.thoughtcrime.securesms.util.DeviceProperties;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.Environment;
|
||||
import org.thoughtcrime.securesms.util.PlayServicesUtil;
|
||||
import org.signal.core.util.PlayServicesUtil;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
|
||||
import org.thoughtcrime.securesms.util.SignalUncaughtExceptionHandler;
|
||||
|
||||
+1
-1
@@ -6,12 +6,12 @@ import androidx.lifecycle.ViewModelProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.signal.core.util.PlayServicesUtil
|
||||
import org.signal.mediasend.SentMediaQuality
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SettingsValues.ForceWebsocketMode
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.messages.IncomingMessageObserver
|
||||
import org.thoughtcrime.securesms.util.PlayServicesUtil
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.webrtc.CallDataMode
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.messages.IncomingMessageObserver;
|
||||
import org.thoughtcrime.securesms.net.SignalNetwork;
|
||||
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||
import org.thoughtcrime.securesms.util.PlayServicesUtil;
|
||||
import org.signal.core.util.PlayServicesUtil;
|
||||
import org.whispersystems.signalservice.api.NetworkResultUtil;
|
||||
import org.signal.network.exceptions.NonSuccessfulResponseCodeException;
|
||||
|
||||
|
||||
+1
-1
@@ -38,6 +38,7 @@ import com.google.i18n.phonenumbers.NumberParseException
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
||||
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber
|
||||
import org.signal.core.ui.logging.LoggingFragment
|
||||
import org.signal.core.util.PlayServicesUtil
|
||||
import org.signal.core.util.ThreadUtil
|
||||
import org.signal.core.util.getParcelableCompat
|
||||
import org.signal.core.util.isNotNullOrBlank
|
||||
@@ -64,7 +65,6 @@ import org.thoughtcrime.securesms.registration.ui.toE164
|
||||
import org.thoughtcrime.securesms.registration.util.CountryPrefix
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
import org.thoughtcrime.securesms.util.Dialogs
|
||||
import org.thoughtcrime.securesms.util.PlayServicesUtil
|
||||
import org.thoughtcrime.securesms.util.SignalE164Util
|
||||
import org.thoughtcrime.securesms.util.SpanUtil
|
||||
import org.thoughtcrime.securesms.util.SupportEmailUtil
|
||||
|
||||
@@ -20,6 +20,7 @@ dependencies {
|
||||
implementation(libs.jackson.core)
|
||||
implementation(libs.jackson.module.kotlin)
|
||||
implementation(libs.google.libphonenumber)
|
||||
implementation(libs.google.play.services.base)
|
||||
testImplementation(libs.androidx.sqlite.framework)
|
||||
|
||||
testImplementation(testLibs.junit.junit)
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
package org.signal.core.util;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
@@ -87,6 +87,10 @@ dependencies {
|
||||
// Phone number hint
|
||||
implementation(libs.google.play.services.auth)
|
||||
|
||||
// Credential Manager (password manager retrieval)
|
||||
implementation(libs.androidx.credentials)
|
||||
implementation(libs.androidx.credentials.compat)
|
||||
|
||||
// Testing
|
||||
testImplementation(testFixtures(project(":core:ui")))
|
||||
testImplementation(testLibs.junit.junit)
|
||||
|
||||
+10
-3
@@ -107,6 +107,7 @@ import org.signal.registration.screens.verificationcode.VerificationCodeViewMode
|
||||
import org.signal.registration.screens.welcome.WelcomeScreen
|
||||
import org.signal.registration.screens.welcome.WelcomeScreenEvents
|
||||
import org.signal.registration.util.AccountEntropyPoolParceler
|
||||
import org.signal.registration.util.RegistrationCredentialManager
|
||||
|
||||
/**
|
||||
* Navigation routes for the registration flow.
|
||||
@@ -756,11 +757,13 @@ private fun EntryProviderScope<NavKey>.navigationEntries(
|
||||
|
||||
// -- Enter AEP
|
||||
entry<RegistrationRoute.EnterAepForLocalBackup> {
|
||||
val context = LocalContext.current
|
||||
val viewModel: EnterAepForLocalBackupViewModel = viewModel(
|
||||
factory = EnterAepForLocalBackupViewModel.Factory(
|
||||
parentEventEmitter = registrationViewModel::onEvent,
|
||||
resultBus = registrationViewModel.resultBus,
|
||||
resultKey = BACKUP_CREDENTIAL_RESULT
|
||||
resultKey = BACKUP_CREDENTIAL_RESULT,
|
||||
isPasswordManagerAvailable = RegistrationCredentialManager.isSupported(context)
|
||||
)
|
||||
)
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
@@ -772,11 +775,13 @@ private fun EntryProviderScope<NavKey>.navigationEntries(
|
||||
}
|
||||
|
||||
entry<RegistrationRoute.EnterAepForRemoteBackupPreRegistration> { key ->
|
||||
val context = LocalContext.current
|
||||
val viewModel: EnterAepForRemoteBackupPreRegistrationViewModel = viewModel(
|
||||
factory = EnterAepForRemoteBackupPreRegistrationViewModel.Factory(
|
||||
e164 = key.e164,
|
||||
repository = registrationRepository,
|
||||
parentEventEmitter = registrationViewModel::onEvent
|
||||
parentEventEmitter = registrationViewModel::onEvent,
|
||||
isPasswordManagerAvailable = RegistrationCredentialManager.isSupported(context)
|
||||
)
|
||||
)
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
@@ -788,9 +793,11 @@ private fun EntryProviderScope<NavKey>.navigationEntries(
|
||||
}
|
||||
|
||||
entry<RegistrationRoute.EnterAepForRemoteBackupPostRegistration> {
|
||||
val context = LocalContext.current
|
||||
val viewModel: EnterAepForRemoteBackupPostRegistrationViewModel = viewModel(
|
||||
factory = EnterAepForRemoteBackupPostRegistrationViewModel.Factory(
|
||||
parentEventEmitter = registrationViewModel::onEvent
|
||||
parentEventEmitter = registrationViewModel::onEvent,
|
||||
isPasswordManagerAvailable = RegistrationCredentialManager.isSupported(context)
|
||||
)
|
||||
)
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
+6
-4
@@ -19,14 +19,15 @@ import org.signal.registration.screens.util.navigateBack
|
||||
class EnterAepForLocalBackupViewModel(
|
||||
private val parentEventEmitter: (RegistrationFlowEvent) -> Unit,
|
||||
private val resultBus: ResultEventBus,
|
||||
private val resultKey: String
|
||||
private val resultKey: String,
|
||||
isPasswordManagerAvailable: Boolean = false
|
||||
) : ViewModel() {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(EnterAepForLocalBackupViewModel::class)
|
||||
}
|
||||
|
||||
private val _state = MutableStateFlow(EnterAepState())
|
||||
private val _state = MutableStateFlow(EnterAepState(isPasswordManagerAvailable = isPasswordManagerAvailable))
|
||||
val state: StateFlow<EnterAepState> = _state.asStateFlow()
|
||||
|
||||
fun onEvent(event: EnterAepEvents) {
|
||||
@@ -53,10 +54,11 @@ class EnterAepForLocalBackupViewModel(
|
||||
class Factory(
|
||||
private val parentEventEmitter: (RegistrationFlowEvent) -> Unit,
|
||||
private val resultBus: ResultEventBus,
|
||||
private val resultKey: String
|
||||
private val resultKey: String,
|
||||
private val isPasswordManagerAvailable: Boolean = false
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return EnterAepForLocalBackupViewModel(parentEventEmitter, resultBus, resultKey) as T
|
||||
return EnterAepForLocalBackupViewModel(parentEventEmitter, resultBus, resultKey, isPasswordManagerAvailable) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-4
@@ -18,14 +18,15 @@ import org.signal.registration.RegistrationRoute
|
||||
import org.signal.registration.screens.util.navigateTo
|
||||
|
||||
class EnterAepForRemoteBackupPostRegistrationViewModel(
|
||||
private val parentEventEmitter: (RegistrationFlowEvent) -> Unit
|
||||
private val parentEventEmitter: (RegistrationFlowEvent) -> Unit,
|
||||
isPasswordManagerAvailable: Boolean = false
|
||||
) : ViewModel() {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(EnterAepForRemoteBackupPostRegistrationViewModel::class)
|
||||
}
|
||||
|
||||
private val _state = MutableStateFlow(EnterAepState())
|
||||
private val _state = MutableStateFlow(EnterAepState(isPasswordManagerAvailable = isPasswordManagerAvailable))
|
||||
val state: StateFlow<EnterAepState> = _state.asStateFlow()
|
||||
|
||||
fun onEvent(event: EnterAepEvents) {
|
||||
@@ -51,10 +52,11 @@ class EnterAepForRemoteBackupPostRegistrationViewModel(
|
||||
}
|
||||
|
||||
class Factory(
|
||||
private val parentEventEmitter: (RegistrationFlowEvent) -> Unit
|
||||
private val parentEventEmitter: (RegistrationFlowEvent) -> Unit,
|
||||
private val isPasswordManagerAvailable: Boolean = false
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return EnterAepForRemoteBackupPostRegistrationViewModel(parentEventEmitter) as T
|
||||
return EnterAepForRemoteBackupPostRegistrationViewModel(parentEventEmitter, isPasswordManagerAvailable) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-5
@@ -24,14 +24,15 @@ import org.signal.registration.screens.util.navigateTo
|
||||
class EnterAepForRemoteBackupPreRegistrationViewModel(
|
||||
private val e164: String,
|
||||
private val repository: RegistrationRepository,
|
||||
private val parentEventEmitter: (RegistrationFlowEvent) -> Unit
|
||||
private val parentEventEmitter: (RegistrationFlowEvent) -> Unit,
|
||||
isPasswordManagerAvailable: Boolean = false
|
||||
) : EventDrivenViewModel<EnterAepEvents>(TAG) {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(EnterAepForRemoteBackupPreRegistrationViewModel::class)
|
||||
}
|
||||
|
||||
private val _state = MutableStateFlow(EnterAepState())
|
||||
private val _state = MutableStateFlow(EnterAepState(isPasswordManagerAvailable = isPasswordManagerAvailable))
|
||||
|
||||
val state: StateFlow<EnterAepState> = _state.asStateFlow()
|
||||
|
||||
@@ -80,7 +81,7 @@ class EnterAepForRemoteBackupPreRegistrationViewModel(
|
||||
is RequestResult.NonSuccess -> {
|
||||
when (val error = result.error) {
|
||||
is NetworkController.RegisterAccountError.RegistrationRecoveryPasswordIncorrect -> {
|
||||
Log.w(TAG, "[Submit] RRP incorrect.")
|
||||
Log.w(TAG, "[Submit] RRP incorrect. Message: ${error.message}")
|
||||
stateEmitter(
|
||||
inputState.copy(
|
||||
isRegistering = false,
|
||||
@@ -134,10 +135,11 @@ class EnterAepForRemoteBackupPreRegistrationViewModel(
|
||||
class Factory(
|
||||
private val e164: String,
|
||||
private val repository: RegistrationRepository,
|
||||
private val parentEventEmitter: (RegistrationFlowEvent) -> Unit
|
||||
private val parentEventEmitter: (RegistrationFlowEvent) -> Unit,
|
||||
private val isPasswordManagerAvailable: Boolean = false
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return EnterAepForRemoteBackupPreRegistrationViewModel(e164, repository, parentEventEmitter) as T
|
||||
return EnterAepForRemoteBackupPreRegistrationViewModel(e164, repository, parentEventEmitter, isPasswordManagerAvailable) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+35
-3
@@ -29,12 +29,14 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
@@ -46,6 +48,7 @@ import androidx.compose.ui.text.input.TransformedText
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlinx.coroutines.launch
|
||||
import org.signal.core.ui.compose.AllDevicePreviews
|
||||
import org.signal.core.ui.compose.Buttons
|
||||
import org.signal.core.ui.compose.Previews
|
||||
@@ -57,6 +60,7 @@ import org.signal.registration.screens.TwoPaneRegistrationScaffold
|
||||
import org.signal.registration.screens.attachDebugLogHelper
|
||||
import org.signal.registration.screens.localbackuprestore.attachBackupKeyAutoFillHelper
|
||||
import org.signal.registration.screens.localbackuprestore.backupKeyAutoFillHelper
|
||||
import org.signal.registration.util.RegistrationCredentialManager
|
||||
|
||||
@Composable
|
||||
fun EnterAepScreen(
|
||||
@@ -93,6 +97,9 @@ private fun OnePaneLayout(
|
||||
Description()
|
||||
Spacer(modifier = Modifier.size(24.dp))
|
||||
RecoveryKeyTextField(state, onEvent)
|
||||
if (state.isPasswordManagerAvailable) {
|
||||
FillFromPasswordManagerButton(onEvent)
|
||||
}
|
||||
}
|
||||
},
|
||||
footer = {
|
||||
@@ -156,6 +163,9 @@ private fun TwoPaneLayout(
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
RecoveryKeyTextField(state, onEvent)
|
||||
if (state.isPasswordManagerAvailable) {
|
||||
FillFromPasswordManagerButton(onEvent)
|
||||
}
|
||||
}
|
||||
},
|
||||
footer = {
|
||||
@@ -259,6 +269,26 @@ private fun RecoveryKeyTextField(state: EnterAepState, onEvent: (EnterAepEvents)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FillFromPasswordManagerButton(onEvent: (EnterAepEvents) -> Unit, modifier: Modifier = Modifier) {
|
||||
val context = LocalContext.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
Buttons.MediumTonal(
|
||||
modifier = modifier,
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
val password = RegistrationCredentialManager.getPasswordCredential(context)
|
||||
if (password != null) {
|
||||
onEvent(EnterAepEvents.BackupKeyChanged(password))
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(R.string.EnterAepScreen__fill_from_password_manager))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NoRecoverKeyButton(onEvent: (EnterAepEvents) -> Unit, modifier: Modifier = Modifier) {
|
||||
TextButton(
|
||||
@@ -324,7 +354,7 @@ internal class AepVisualTransformation(private val chunkSize: Int) : VisualTrans
|
||||
private fun EnterAepScreenPreview() {
|
||||
Previews.Preview {
|
||||
EnterAepScreen(
|
||||
state = EnterAepState(),
|
||||
state = EnterAepState(isPasswordManagerAvailable = true),
|
||||
onEvent = {}
|
||||
)
|
||||
}
|
||||
@@ -338,7 +368,8 @@ private fun EnterAepScreenFilledPreview() {
|
||||
state = EnterAepState(
|
||||
enteredText = "uy38jh2778hjjhj8lk19ga61s672jsj089r023s6a57809bap92j2yh5t326vv7t",
|
||||
backupKey = "uy38jh2778hjjhj8lk19ga61s672jsj089r023s6a57809bap92j2yh5t326vv7t",
|
||||
isBackupKeyValid = true
|
||||
isBackupKeyValid = true,
|
||||
isPasswordManagerAvailable = true
|
||||
),
|
||||
onEvent = {}
|
||||
)
|
||||
@@ -354,7 +385,8 @@ private fun EnterAepScreenErrorPreview() {
|
||||
enteredText = "uy38jh2778hjjhj8lk19ga61s672jsj089r023s6a57809bap92j2yh5t326vv7t",
|
||||
backupKey = "uy38jh2778hjjhj8lk19ga61s672jsj089r023s6a57809bap92j2yh5t326vv7t",
|
||||
isBackupKeyValid = false,
|
||||
aepValidationError = AepValidationError.Invalid
|
||||
aepValidationError = AepValidationError.Invalid,
|
||||
isPasswordManagerAvailable = true
|
||||
),
|
||||
onEvent = {}
|
||||
)
|
||||
|
||||
+4
-2
@@ -16,9 +16,11 @@ data class EnterAepState(
|
||||
val aepValidationError: AepValidationError? = null,
|
||||
val chunkLength: Int = 4,
|
||||
val isRegistering: Boolean = false,
|
||||
val registrationError: RegistrationError? = null
|
||||
val registrationError: RegistrationError? = null,
|
||||
/** Whether a password manager / credential provider is available to fill the recovery key. */
|
||||
val isPasswordManagerAvailable: Boolean = false
|
||||
) {
|
||||
override fun toString(): String = "EnterAepState(enteredText=${enteredText.censor()}, backupKey=${backupKey.censor()}, isBackupKeyValid=$isBackupKeyValid, aepValidationError=$aepValidationError, chunkLength=$chunkLength, isRegistering=$isRegistering, registrationError=$registrationError)"
|
||||
override fun toString(): String = "EnterAepState(enteredText=${enteredText.censor()}, backupKey=${backupKey.censor()}, isBackupKeyValid=$isBackupKeyValid, aepValidationError=$aepValidationError, chunkLength=$chunkLength, isRegistering=$isRegistering, registrationError=$registrationError, isPasswordManagerAvailable=$isPasswordManagerAvailable)"
|
||||
}
|
||||
|
||||
sealed interface AepValidationError {
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.registration.util
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.view.autofill.AutofillManager
|
||||
import androidx.annotation.UiContext
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.credentials.CredentialManager
|
||||
import androidx.credentials.GetCredentialRequest
|
||||
import androidx.credentials.GetPasswordOption
|
||||
import androidx.credentials.PasswordCredential
|
||||
import androidx.credentials.exceptions.GetCredentialException
|
||||
import org.signal.core.util.PlayServicesUtil
|
||||
import org.signal.core.util.logging.Log
|
||||
|
||||
/**
|
||||
* Retrieves a previously saved password credential (e.g. a backup/recovery key) using Android's
|
||||
* Credential Manager.
|
||||
*/
|
||||
object RegistrationCredentialManager {
|
||||
|
||||
private val TAG = Log.tag(RegistrationCredentialManager::class)
|
||||
|
||||
/**
|
||||
* Whether a password manager / credential provider is available to fill credentials. On API 26+
|
||||
* this tracks whether the user has an autofill service enabled; older devices fall back to the
|
||||
* Credential Manager Play Services backend, so they are supported only when Play Services is.
|
||||
*/
|
||||
fun isSupported(context: Context): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= 26) {
|
||||
context.getSystemService<AutofillManager>()?.isEnabled == true
|
||||
} else {
|
||||
PlayServicesUtil.getPlayServicesStatus(context) == PlayServicesUtil.PlayServicesStatus.SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts the device password manager to let the user pick a saved password credential and returns
|
||||
* its value, or null if none was chosen or retrieval failed. Must be called with an Activity
|
||||
* context so the Credential Manager UI can be shown.
|
||||
*/
|
||||
suspend fun getPasswordCredential(@UiContext activityContext: Context): String? {
|
||||
return try {
|
||||
val result = CredentialManager.create(activityContext).getCredential(activityContext, GetCredentialRequest(listOf(GetPasswordOption())))
|
||||
(result.credential as? PasswordCredential)?.password
|
||||
} catch (e: GetCredentialException) {
|
||||
Log.w(TAG, "Failed to retrieve credential from password manager.", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,6 +204,8 @@
|
||||
<string name="EnterAepScreen__invalid_recovery_key">Invalid recovery key</string>
|
||||
<!-- Error text when key is correct format but does not match account -->
|
||||
<string name="EnterAepScreen__incorrect_recovery_key">Incorrect recovery key</string>
|
||||
<!-- Button that prompts the device password manager to fill in the recovery key -->
|
||||
<string name="EnterAepScreen__fill_from_password_manager">Fill from password manager</string>
|
||||
<!-- Text shown while preparing the restore -->
|
||||
<string name="LocalBackupRestoreScreen__preparing_restore">Preparing restore…</string>
|
||||
<!-- Title shown while restore is in progress -->
|
||||
|
||||
@@ -4365,6 +4365,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
||||
<sha256 value="c4327257a478febf00da8b921e39052b6aa44db9cf61e00c3d4bf77ec8091ce7" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.google.android.libraries.identity.googleid" name="googleid" version="1.1.0">
|
||||
<artifact name="googleid-1.1.0.aar">
|
||||
<sha256 value="194ac1fc1986dd1f62046fae37ddf77e63770fdc1f3d34baaa397cfbf4d191a2" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.google.android.libraries.identity.googleid" name="googleid" version="1.2.0">
|
||||
<artifact name="googleid-1.2.0.aar">
|
||||
<sha256 value="f49e88107f90ef0d1250d8504e8eba18be8532b1286203c92f28bf6bd15814ff" origin="Generated by Gradle"/>
|
||||
|
||||
Reference in New Issue
Block a user