Merge "Fix full-charge timestamp lost in battery chart graph." into main
diff --git a/aconfig/OWNERS b/aconfig/OWNERS
index 1131545..c26a190 100644
--- a/aconfig/OWNERS
+++ b/aconfig/OWNERS
@@ -1 +1,3 @@
per-file settings_accessibility_flag_declarations.aconfig = file:/src/com/android/settings/accessibility/OWNERS
+per-file settings_biometrics_integration_declarations.aconfig = file:platform/vendor/unbundled_google/packages/SettingsGoogle:/future/biometrics/OWNERS
+
diff --git a/aconfig/settings_voice_activation_apps_flag_declarations.aconfig b/aconfig/settings_voice_activation_apps_flag_declarations.aconfig
index dccc805..d98bc52 100644
--- a/aconfig/settings_voice_activation_apps_flag_declarations.aconfig
+++ b/aconfig/settings_voice_activation_apps_flag_declarations.aconfig
@@ -1,8 +1,8 @@
package: "com.android.settings.flags"
flag {
- name: "enable_voice_activation_apps_special_app_access"
- namespace: "voice_activation_apps"
- description: "Enable voice activation apps in Special app access"
+ name: "enable_voice_activation_apps_in_settings"
+ namespace: "permissions"
+ description: "Enable voice activation apps in Settings"
bug: "303727896"
}
\ No newline at end of file
diff --git a/res/layout/fingerprint_v2_rfps_enroll_enrolling.xml b/res/layout/fingerprint_v2_rfps_enroll_enrolling.xml
new file mode 100644
index 0000000..0b087d2
--- /dev/null
+++ b/res/layout/fingerprint_v2_rfps_enroll_enrolling.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.google.android.setupdesign.GlifLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ style="?attr/fingerprint_layout_theme"
+ android:id="@+id/setup_wizard_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ >
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/SudContentFrame"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:gravity="center"
+ android:orientation="vertical">
+
+ <com.google.android.setupdesign.view.FillContentLayout
+ android:layout_width="@dimen/fingerprint_progress_bar_max_size"
+ android:layout_height="@dimen/fingerprint_progress_bar_max_size"
+ android:layout_marginVertical="24dp"
+ android:paddingTop="0dp"
+ android:paddingBottom="0dp">
+
+ <com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.RFPSProgressBar
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/fingerprint_progress_bar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/fp_illustration"
+ android:minHeight="@dimen/fingerprint_progress_bar_min_size"
+ android:progress="0" />
+
+ </com.google.android.setupdesign.view.FillContentLayout>
+
+ <TextView
+ android:id="@+id/text"
+ style="@style/TextAppearance.ErrorText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal|bottom"
+ android:accessibilityLiveRegion="polite"
+ android:gravity="center"
+ android:visibility="invisible" />
+
+ </LinearLayout>
+
+</LinearLayout>
+
+</com.google.android.setupdesign.GlifLayout>
\ No newline at end of file
diff --git a/res/layout/preference_widget_qrcode.xml b/res/layout/preference_widget_qrcode.xml
new file mode 100644
index 0000000..7994fe1
--- /dev/null
+++ b/res/layout/preference_widget_qrcode.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/button_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="@dimen/two_target_min_width"
+ android:minHeight="@dimen/min_tap_target_size"
+ android:layout_gravity="center"
+ android:background="?android:attr/selectableItemBackground"/>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1eb58ee..a3760c5 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1212,7 +1212,7 @@
<string name="private_space_hide_title">Hide when locked</string>
<!-- Title for the hide Private Space setting. [CHAR LIMIT=60] -->
<string name="privatespace_hide_page_title">Hide Private Space when locked</string>
- <!-- Description for hide Private Space settings page. [CHAR LIMIT=60] -->
+ <!-- Description for hide Private Space settings page. [CHAR LIMIT=NONE] -->
<string name="privatespace_hide_page_summary">To stop other people knowing Private Space is on your device, you can hide it from your apps list</string>
<!-- Header in hide Private Space settings page to access Private Space when hidden. [CHAR LIMIT=60] -->
<string name="privatespace_access_header">Access Private Space when hidden</string>
@@ -3169,6 +3169,8 @@
<string name="apn_settings">APNs</string>
<!-- Screen title after user selects APNs setting option -->
<string name="apn_edit">Edit access point</string>
+ <!-- Screen title after user selects add APNs setting -->
+ <string name="apn_add">Add access point</string>
<!-- Edit access point label summary text when no value has been set -->
<string name="apn_not_set">Not set</string>
<!-- Edit access point label summary text when no value has been set for mvno value. [CHAR LIMIT=NONE]-->
diff --git a/res/xml/bluetooth_audio_sharing.xml b/res/xml/bluetooth_audio_sharing.xml
index 86bb062..ca7137a 100644
--- a/res/xml/bluetooth_audio_sharing.xml
+++ b/res/xml/bluetooth_audio_sharing.xml
@@ -26,6 +26,12 @@
settings:controller="com.android.settings.connecteddevice.audiosharing.CallsAndAlarmsPreferenceController"
android:summary=""/>
+ <com.android.settings.connecteddevice.audiosharing.AudioSharingNamePreference
+ android:key="audio_sharing_stream_name"
+ android:title="Stream name"
+ android:summary="********"
+ settings:controller="com.android.settings.connecteddevice.audiosharing.AudioSharingNamePreferenceController"/>
+
<PreferenceCategory
android:key="audio_streams_settings_category"
android:title="@string/audio_sharing_streams_category_title"
@@ -38,5 +44,4 @@
android:icon="@drawable/ic_chevron_right_24dp" />
</PreferenceCategory>
-
</PreferenceScreen>
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics/fingerprint2/conversion/Util.kt b/src/com/android/settings/biometrics/fingerprint2/conversion/Util.kt
index 98b7ed0..58ef509 100644
--- a/src/com/android/settings/biometrics/fingerprint2/conversion/Util.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/conversion/Util.kt
@@ -16,14 +16,61 @@
package com.android.settings.biometrics.fingerprint2.conversion
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_CANCELED
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS
import android.hardware.fingerprint.FingerprintManager
+import com.android.settings.R
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
-class Util
-
-fun EnrollReason.toOriginalReason(): Int {
- return when (this) {
- EnrollReason.EnrollEnrolling -> FingerprintManager.ENROLL_ENROLL
- EnrollReason.FindSensor -> FingerprintManager.ENROLL_FIND_SENSOR
+object Util {
+ fun EnrollReason.toOriginalReason(): Int {
+ return when (this) {
+ EnrollReason.EnrollEnrolling -> FingerprintManager.ENROLL_ENROLL
+ EnrollReason.FindSensor -> FingerprintManager.ENROLL_FIND_SENSOR
+ }
}
+
+ fun Int.toEnrollError(isSetupWizard: Boolean): FingerEnrollState.EnrollError {
+ val errTitle =
+ when (this) {
+ FingerprintManager.FINGERPRINT_ERROR_TIMEOUT ->
+ R.string.security_settings_fingerprint_enroll_error_dialog_title
+ FingerprintManager.FINGERPRINT_ERROR_BAD_CALIBRATION ->
+ R.string.security_settings_fingerprint_bad_calibration_title
+ else -> R.string.security_settings_fingerprint_enroll_error_unable_to_process_dialog_title
+ }
+ val errString =
+ if (isSetupWizard) {
+ when (this) {
+ FingerprintManager.FINGERPRINT_ERROR_TIMEOUT ->
+ R.string.security_settings_fingerprint_enroll_error_dialog_title
+ FingerprintManager.FINGERPRINT_ERROR_BAD_CALIBRATION ->
+ R.string.security_settings_fingerprint_bad_calibration_title
+ else -> R.string.security_settings_fingerprint_enroll_error_unable_to_process_dialog_title
+ }
+ } else {
+ when (this) {
+ // This message happens when the underlying crypto layer
+ // decides to revoke the enrollment auth token
+ FingerprintManager.FINGERPRINT_ERROR_TIMEOUT ->
+ R.string.security_settings_fingerprint_enroll_error_timeout_dialog_message
+ FingerprintManager.FINGERPRINT_ERROR_BAD_CALIBRATION ->
+ R.string.security_settings_fingerprint_bad_calibration
+ FingerprintManager.FINGERPRINT_ERROR_UNABLE_TO_PROCESS ->
+ R.string.security_settings_fingerprint_enroll_error_unable_to_process_message
+ // There's nothing specific to tell the user about. Ask them to try again.
+ else -> R.string.security_settings_fingerprint_enroll_error_generic_dialog_message
+ }
+ }
+
+ return FingerEnrollState.EnrollError(
+ errTitle,
+ errString,
+ this == FINGERPRINT_ERROR_UNABLE_TO_PROCESS,
+ this == FINGERPRINT_ERROR_CANCELED,
+ )
+ }
+
}
+
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt
index 5c9232f..984d04c 100644
--- a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt
@@ -24,12 +24,16 @@
import android.os.CancellationSignal
import android.util.Log
import com.android.settings.biometrics.GatekeeperPasswordProvider
-import com.android.settings.biometrics.fingerprint2.conversion.toOriginalReason
+import com.android.settings.biometrics.fingerprint2.conversion.Util.toEnrollError
+import com.android.settings.biometrics.fingerprint2.conversion.Util.toOriginalReason
+import com.android.settings.biometrics.fingerprint2.shared.data.repository.PressToAuthProvider
import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintFlow
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
+import com.android.settings.biometrics.fingerprint2.shared.model.SetupWizard
import com.android.settings.password.ChooseLockSettingsHelper
import com.android.systemui.biometrics.shared.model.toFingerprintSensor
import kotlin.coroutines.resume
@@ -38,9 +42,12 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.onFailure
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
@@ -51,7 +58,8 @@
private val backgroundDispatcher: CoroutineDispatcher,
private val fingerprintManager: FingerprintManager,
private val gatekeeperPasswordProvider: GatekeeperPasswordProvider,
- private val pressToAuthProvider: () -> Boolean,
+ private val pressToAuthProvider: PressToAuthProvider,
+ private val fingerprintFlow: FingerprintFlow,
) : FingerprintManagerInteractor {
private val maxFingerprints =
@@ -60,6 +68,8 @@
)
private val applicationContext = applicationContext.applicationContext
+ private val enrollRequestOutstanding = MutableStateFlow(false)
+
override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> =
suspendCoroutine {
val callback = GenerateChallengeCallback { _, userId, challenge ->
@@ -75,11 +85,11 @@
fingerprintManager.generateChallenge(applicationContext.userId, callback)
}
- override val enrolledFingerprints: Flow<List<FingerprintViewModel>> = flow {
+ override val enrolledFingerprints: Flow<List<FingerprintData>> = flow {
emit(
fingerprintManager
.getEnrolledFingerprints(applicationContext.userId)
- .map { (FingerprintViewModel(it.name.toString(), it.biometricId, it.deviceId)) }
+ .map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) }
.toList()
)
}
@@ -103,28 +113,51 @@
override suspend fun enroll(
hardwareAuthToken: ByteArray?,
enrollReason: EnrollReason,
- ): Flow<FingerEnrollStateViewModel> = callbackFlow {
+ ): Flow<FingerEnrollState> = callbackFlow {
+ // TODO (b/308456120) Improve this logic
+ if (enrollRequestOutstanding.value) {
+ Log.d(TAG, "Outstanding enroll request, waiting 150ms")
+ delay(150)
+ if (enrollRequestOutstanding.value) {
+ Log.e(TAG, "Request still present, continuing")
+ }
+ }
+
+ enrollRequestOutstanding.update { true }
+
var streamEnded = false
+ var totalSteps: Int? = null
val enrollmentCallback =
object : FingerprintManager.EnrollmentCallback() {
override fun onEnrollmentProgress(remaining: Int) {
- trySend(FingerEnrollStateViewModel.EnrollProgress(remaining)).onFailure { error ->
+ // This is sort of an implementation detail, but unfortunately the API isn't
+ // very expressive. If anything we should look at changing the FingerprintManager API.
+ if (totalSteps == null) {
+ totalSteps = remaining + 1
+ }
+
+ trySend(FingerEnrollState.EnrollProgress(remaining, totalSteps!!)).onFailure {
+ error ->
Log.d(TAG, "onEnrollmentProgress($remaining) failed to send, due to $error")
}
+
if (remaining == 0) {
streamEnded = true
+ enrollRequestOutstanding.update { false }
}
}
override fun onEnrollmentHelp(helpMsgId: Int, helpString: CharSequence?) {
- trySend(FingerEnrollStateViewModel.EnrollHelp(helpMsgId, helpString.toString()))
+ trySend(FingerEnrollState.EnrollHelp(helpMsgId, helpString.toString()))
.onFailure { error -> Log.d(TAG, "onEnrollmentHelp failed to send, due to $error") }
}
override fun onEnrollmentError(errMsgId: Int, errString: CharSequence?) {
- trySend(FingerEnrollStateViewModel.EnrollError(errMsgId, errString.toString()))
+ trySend(errMsgId.toEnrollError(fingerprintFlow == SetupWizard))
.onFailure { error -> Log.d(TAG, "onEnrollmentError failed to send, due to $error") }
+ Log.d(TAG, "onEnrollmentError($errMsgId)")
streamEnded = true
+ enrollRequestOutstanding.update { false }
}
}
@@ -140,12 +173,13 @@
// If the stream has not been ended, and the user has stopped collecting the flow
// before it was over, send cancel.
if (!streamEnded) {
+ Log.e(TAG, "Cancel is sent from settings for enroll()")
cancellationSignal.cancel()
}
}
}
- override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean = suspendCoroutine {
+ override suspend fun removeFingerprint(fp: FingerprintData): Boolean = suspendCoroutine {
val callback =
object : RemovalCallback() {
override fun onRemovalError(
@@ -170,7 +204,7 @@
)
}
- override suspend fun renameFingerprint(fp: FingerprintViewModel, newName: String) {
+ override suspend fun renameFingerprint(fp: FingerprintData, newName: String) {
withContext(backgroundDispatcher) {
fingerprintManager.rename(fp.fingerId, applicationContext.userId, newName)
}
@@ -181,11 +215,11 @@
}
override suspend fun pressToAuthEnabled(): Boolean = suspendCancellableCoroutine {
- it.resume(pressToAuthProvider())
+ it.resume(pressToAuthProvider.isEnabled)
}
- override suspend fun authenticate(): FingerprintAuthAttemptViewModel =
- suspendCancellableCoroutine { c: CancellableContinuation<FingerprintAuthAttemptViewModel> ->
+ override suspend fun authenticate(): FingerprintAuthAttemptModel =
+ suspendCancellableCoroutine { c: CancellableContinuation<FingerprintAuthAttemptModel> ->
val authenticationCallback =
object : FingerprintManager.AuthenticationCallback() {
@@ -195,7 +229,7 @@
Log.d(TAG, "framework sent down onAuthError after finish")
return
}
- c.resume(FingerprintAuthAttemptViewModel.Error(errorCode, errString.toString()))
+ c.resume(FingerprintAuthAttemptModel.Error(errorCode, errString.toString()))
}
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
@@ -204,7 +238,7 @@
Log.d(TAG, "framework sent down onAuthError after finish")
return
}
- c.resume(FingerprintAuthAttemptViewModel.Success(result.fingerprint?.biometricId ?: -1))
+ c.resume(FingerprintAuthAttemptModel.Success(result.fingerprint?.biometricId ?: -1))
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/repository/PressToAuthProviderImpl.kt b/src/com/android/settings/biometrics/fingerprint2/repository/PressToAuthProviderImpl.kt
new file mode 100644
index 0000000..38c5335
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/repository/PressToAuthProviderImpl.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.repository
+
+import android.content.Context
+import android.provider.Settings
+import com.android.settings.biometrics.fingerprint2.shared.data.repository.PressToAuthProvider
+
+class PressToAuthProviderImpl(val context: Context) : PressToAuthProvider {
+ override val isEnabled: Boolean
+ get() {
+ var toReturn: Int =
+ Settings.Secure.getIntForUser(
+ context.contentResolver,
+ Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
+ -1,
+ context.userId,
+ )
+ if (toReturn == -1) {
+ toReturn =
+ if (
+ context.resources.getBoolean(com.android.internal.R.bool.config_performantAuthDefault)
+ ) {
+ 1
+ } else {
+ 0
+ }
+ Settings.Secure.putIntForUser(
+ context.contentResolver,
+ Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
+ toReturn,
+ context.userId
+ )
+ }
+ return (toReturn == 1)
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/shared/data/repository/PressToAuthProvider.kt
similarity index 60%
copy from src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintViewModel.kt
copy to src/com/android/settings/biometrics/fingerprint2/shared/data/repository/PressToAuthProvider.kt
index db28e79..e776b9a 100644
--- a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/shared/data/repository/PressToAuthProvider.kt
@@ -14,21 +14,14 @@
* limitations under the License.
*/
-package com.android.settings.biometrics.fingerprint2.shared.model
+package com.android.settings.biometrics.fingerprint2.shared.data.repository
-data class FingerprintViewModel(
- val name: String,
- val fingerId: Int,
- val deviceId: Long,
-)
-
-sealed class FingerprintAuthAttemptViewModel {
- data class Success(
- val fingerId: Int,
- ) : FingerprintAuthAttemptViewModel()
-
- data class Error(
- val error: Int,
- val message: String,
- ) : FingerprintAuthAttemptViewModel()
-}
+/**
+ * Interface that indicates if press to auth is on or off.
+ */
+interface PressToAuthProvider {
+ /**
+ * Indicates true if the PressToAuth feature is enabled, false otherwise.
+ */
+ val isEnabled: Boolean
+}
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/domain/interactor/FingerprintManagerInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/shared/domain/interactor/FingerprintManagerInteractor.kt
index 7286715..94afa49 100644
--- a/src/com/android/settings/biometrics/fingerprint2/shared/domain/interactor/FingerprintManagerInteractor.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/shared/domain/interactor/FingerprintManagerInteractor.kt
@@ -17,9 +17,9 @@
package com.android.settings.biometrics.fingerprint2.shared.domain.interactor
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
import com.android.systemui.biometrics.shared.model.FingerprintSensor
import kotlinx.coroutines.flow.Flow
@@ -31,7 +31,7 @@
*/
interface FingerprintManagerInteractor {
/** Returns the list of current fingerprints. */
- val enrolledFingerprints: Flow<List<FingerprintViewModel>>
+ val enrolledFingerprints: Flow<List<FingerprintData>>
/** Returns the max enrollable fingerprints, note during SUW this might be 1 */
val maxEnrollableFingerprints: Flow<Int>
@@ -43,7 +43,7 @@
val sensorPropertiesInternal: Flow<FingerprintSensor?>
/** Runs the authenticate flow */
- suspend fun authenticate(): FingerprintAuthAttemptViewModel
+ suspend fun authenticate(): FingerprintAuthAttemptModel
/**
* Generates a challenge with the provided [gateKeeperPasswordHandle] and on success returns a
@@ -56,22 +56,22 @@
/**
* Runs [FingerprintManager.enroll] with the [hardwareAuthToken] and [EnrollReason] for this
- * enrollment. Returning the [FingerEnrollStateViewModel] that represents this fingerprint
+ * enrollment. Returning the [FingerEnrollState] that represents this fingerprint
* enrollment state.
*/
suspend fun enroll(
- hardwareAuthToken: ByteArray?,
- enrollReason: EnrollReason,
- ): Flow<FingerEnrollStateViewModel>
+ hardwareAuthToken: ByteArray?,
+ enrollReason: EnrollReason,
+ ): Flow<FingerEnrollState>
/**
* Removes the given fingerprint, returning true if it was successfully removed and false
* otherwise
*/
- suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean
+ suspend fun removeFingerprint(fp: FingerprintData): Boolean
/** Renames the given fingerprint if one exists */
- suspend fun renameFingerprint(fp: FingerprintViewModel, newName: String)
+ suspend fun renameFingerprint(fp: FingerprintData, newName: String)
/** Indicates if the device has side fingerprint */
suspend fun hasSideFps(): Boolean
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/model/EnrollReasonViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/shared/model/EnrollReason.kt
similarity index 100%
rename from src/com/android/settings/biometrics/fingerprint2/shared/model/EnrollReasonViewModel.kt
rename to src/com/android/settings/biometrics/fingerprint2/shared/model/EnrollReason.kt
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerEnrollStateViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerEnrollState.kt
similarity index 73%
rename from src/com/android/settings/biometrics/fingerprint2/shared/model/FingerEnrollStateViewModel.kt
rename to src/com/android/settings/biometrics/fingerprint2/shared/model/FingerEnrollState.kt
index 179ac60..4766d59 100644
--- a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerEnrollStateViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerEnrollState.kt
@@ -22,19 +22,28 @@
* Represents a fingerprint enrollment state. See [FingerprintManager.EnrollmentCallback] for more
* information
*/
-sealed class FingerEnrollStateViewModel {
- /** Represents enrollment step progress. */
+sealed class FingerEnrollState {
+ /**
+ * Represents an enrollment step progress.
+ *
+ * Progress is obtained by (totalStepsRequired - remainingSteps) / totalStepsRequired
+ */
data class EnrollProgress(
val remainingSteps: Int,
- ) : FingerEnrollStateViewModel()
+ val totalStepsRequired: Int,
+ ) : FingerEnrollState()
+
/** Represents that recoverable error has been encountered during enrollment. */
data class EnrollHelp(
@StringRes val helpMsgId: Int,
val helpString: String,
- ) : FingerEnrollStateViewModel()
+ ) : FingerEnrollState()
+
/** Represents that an unrecoverable error has been encountered and the operation is complete. */
data class EnrollError(
- @StringRes val errMsgId: Int,
- val errString: String,
- ) : FingerEnrollStateViewModel()
+ @StringRes val errTitle: Int,
+ @StringRes val errString: Int,
+ val shouldRetryEnrollment: Boolean,
+ val isCancelled: Boolean,
+ ) : FingerEnrollState()
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintData.kt
similarity index 84%
rename from src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintViewModel.kt
rename to src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintData.kt
index db28e79..b2aa25c 100644
--- a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintData.kt
@@ -16,19 +16,19 @@
package com.android.settings.biometrics.fingerprint2.shared.model
-data class FingerprintViewModel(
+data class FingerprintData(
val name: String,
val fingerId: Int,
val deviceId: Long,
)
-sealed class FingerprintAuthAttemptViewModel {
+sealed class FingerprintAuthAttemptModel {
data class Success(
val fingerId: Int,
- ) : FingerprintAuthAttemptViewModel()
+ ) : FingerprintAuthAttemptModel()
data class Error(
val error: Int,
val message: String,
- ) : FingerprintAuthAttemptViewModel()
+ ) : FingerprintAuthAttemptModel()
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintFlow.kt b/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintFlow.kt
new file mode 100644
index 0000000..93c7577
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintFlow.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.shared.model
+
+/**
+ * The [FingerprintFlow] for fingerprint enrollment indicates information on how the flow should behave.
+ */
+sealed class FingerprintFlow
+
+/** The default enrollment experience, typically called from Settings */
+data object Default : FingerprintFlow()
+
+/** SetupWizard/Out of box experience (OOBE) enrollment type. */
+data object SetupWizard : FingerprintFlow()
+
+/** Unicorn enrollment type */
+data object Unicorn : FingerprintFlow()
+
+/** Flow to specify settings type */
+data object Settings : FingerprintFlow()
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt
index 58fcea6..de2a1ee 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt
@@ -16,12 +16,9 @@
package com.android.settings.biometrics.fingerprint2.ui.enrollment.activity
-import android.annotation.ColorInt
import android.app.Activity
import android.content.Intent
-import android.content.res.ColorStateList
import android.content.res.Configuration
-import android.graphics.Color
import android.hardware.fingerprint.FingerprintManager
import android.os.Bundle
import android.provider.Settings
@@ -35,22 +32,27 @@
import com.android.internal.widget.LockPatternUtils
import com.android.settings.R
import com.android.settings.SetupWizardUtils
-import com.android.settings.Utils
import com.android.settings.Utils.SETTINGS_PACKAGE_NAME
import com.android.settings.biometrics.BiometricEnrollBase
import com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST
import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED
import com.android.settings.biometrics.GatekeeperPasswordProvider
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.Default
+import com.android.settings.biometrics.fingerprint2.shared.model.SetupWizard
+import com.android.settings.biometrics.fingerprint2.repository.PressToAuthProviderImpl
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollConfirmationV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollEnrollingV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollFindSensorV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollIntroV2Fragment
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.fragment.RFPSEnrollFragment
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.AccessibilityViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Confirmation
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Education
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Enrollment
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
@@ -65,8 +67,11 @@
import com.android.settings.password.ChooseLockGeneric
import com.android.settings.password.ChooseLockSettingsHelper
import com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.google.android.setupcompat.util.WizardManagerHelper
import com.google.android.setupdesign.util.ThemeHelper
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch
@@ -77,6 +82,7 @@
* children fragments.
*/
class FingerprintEnrollmentV2Activity : FragmentActivity() {
+ private lateinit var fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel
private lateinit var navigationViewModel: FingerprintEnrollNavigationViewModel
private lateinit var gatekeeperViewModel: FingerprintGatekeeperViewModel
private lateinit var fingerprintEnrollViewModel: FingerprintEnrollViewModel
@@ -84,6 +90,7 @@
private lateinit var foldStateViewModel: FoldStateViewModel
private lateinit var orientationStateViewModel: OrientationStateViewModel
private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
+ private lateinit var backgroundViewModel: BackgroundViewModel
private val coroutineDispatcher = Dispatchers.Default
/** Result listener for ChooseLock activity flow. */
@@ -101,23 +108,22 @@
}
}
- override fun onAttachedToWindow() {
- window.statusBarColor = getBackgroundColor()
- super.onAttachedToWindow()
+ override fun onStop() {
+ super.onStop()
+ if (!isChangingConfigurations) {
+ backgroundViewModel.wentToBackground()
+ }
}
+ override fun onResume() {
+ super.onResume()
+ backgroundViewModel.inForeground()
+ }
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
foldStateViewModel.onConfigurationChange(newConfig)
}
- @ColorInt
- private fun getBackgroundColor(): Int {
- val stateList: ColorStateList? =
- Utils.getColorAttr(applicationContext, android.R.attr.windowBackground)
- return stateList?.defaultColor ?: Color.TRANSPARENT
- }
-
private fun onConfirmDevice(resultCode: Int, data: Intent?) {
val wasSuccessful = resultCode == RESULT_FINISHED || resultCode == Activity.RESULT_OK
val gateKeeperPasswordHandle = data?.getExtra(EXTRA_KEY_GK_PW_HANDLE) as Long?
@@ -137,39 +143,28 @@
val context = applicationContext
val fingerprintManager = context.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager
+ val isAnySuw = WizardManagerHelper.isAnySetupWizard(intent)
+ val enrollType =
+ if (isAnySuw) {
+ SetupWizard
+ } else {
+ Default
+ }
+
+ backgroundViewModel =
+ ViewModelProvider(this, BackgroundViewModel.BackgroundViewModelFactory())[
+ BackgroundViewModel::class.java]
+
val interactor =
FingerprintManagerInteractorImpl(
context,
backgroundDispatcher,
fingerprintManager,
- GatekeeperPasswordProvider(LockPatternUtils(context))
- ) {
- var toReturn: Int =
- Settings.Secure.getIntForUser(
- context.contentResolver,
- Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
- -1,
- context.userId,
- )
- if (toReturn == -1) {
- toReturn =
- if (
- context.resources.getBoolean(com.android.internal.R.bool.config_performantAuthDefault)
- ) {
- 1
- } else {
- 0
- }
- Settings.Secure.putIntForUser(
- context.contentResolver,
- Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
- toReturn,
- context.userId
- )
- }
- toReturn == 1
- }
+ GatekeeperPasswordProvider(LockPatternUtils(context)),
+ PressToAuthProviderImpl(context),
+ enrollType,
+ )
var challenge: Long? = intent.getExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE) as Long?
val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
@@ -191,7 +186,8 @@
backgroundDispatcher,
interactor,
gatekeeperViewModel,
- gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo, /* canSkipConfirm */
+ gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo,
+ enrollType,
)
)[FingerprintEnrollNavigationViewModel::class.java]
@@ -207,7 +203,8 @@
this,
FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(
interactor,
- backgroundDispatcher
+ gatekeeperViewModel,
+ navigationViewModel,
)
)[FingerprintEnrollViewModel::class.java]
@@ -230,6 +227,16 @@
ViewModelProvider(this, OrientationStateViewModel.OrientationViewModelFactory(context))[
OrientationStateViewModel::class.java]
+ // Initialize FingerprintEnrollEnrollingViewModel
+ fingerprintEnrollEnrollingViewModel =
+ ViewModelProvider(
+ this,
+ FingerprintEnrollEnrollingViewModel.FingerprintEnrollEnrollingViewModelFactory(
+ fingerprintEnrollViewModel,
+ backgroundViewModel
+ )
+ )[FingerprintEnrollEnrollingViewModel::class.java]
+
// Initialize FingerprintEnrollFindSensorViewModel
ViewModelProvider(
this,
@@ -237,48 +244,65 @@
navigationViewModel,
fingerprintEnrollViewModel,
gatekeeperViewModel,
+ backgroundViewModel,
accessibilityViewModel,
foldStateViewModel,
orientationStateViewModel
)
)[FingerprintEnrollFindSensorViewModel::class.java]
+ // Initialize RFPS View Model
+ ViewModelProvider(
+ this,
+ RFPSViewModel.RFPSViewModelFactory(fingerprintEnrollEnrollingViewModel)
+ )[RFPSViewModel::class.java]
+
lifecycleScope.launch {
- navigationViewModel.navigationViewModel.filterNotNull().collect {
- Log.d(TAG, "navigationStep $it")
- val isForward = it.forward
- val currStep = it.currStep
- val theClass: Class<Fragment>? =
- when (currStep) {
- Confirmation -> FingerprintEnrollConfirmationV2Fragment::class.java as Class<Fragment>
- Education -> FingerprintEnrollFindSensorV2Fragment::class.java as Class<Fragment>
- Enrollment -> FingerprintEnrollEnrollingV2Fragment::class.java as Class<Fragment>
- Intro -> FingerprintEnrollIntroV2Fragment::class.java as Class<Fragment>
- else -> null
- }
-
- if (theClass != null) {
- supportFragmentManager.fragments.onEach { fragment ->
- supportFragmentManager.beginTransaction().remove(fragment).commit()
- }
- supportFragmentManager
- .beginTransaction()
- .setReorderingAllowed(true)
- .add(R.id.fragment_container_view, theClass, null)
- .commit()
- } else {
-
- if (currStep is Finish) {
- if (currStep.resultCode != null) {
- finishActivity(currStep.resultCode)
- } else {
- finish()
+ navigationViewModel.navigationViewModel
+ .filterNotNull()
+ .combine(fingerprintEnrollViewModel.sensorType) { nav, sensorType -> Pair(nav, sensorType) }
+ .collect { (nav, sensorType) ->
+ Log.d(TAG, "navigationStep $nav")
+ fingerprintEnrollViewModel.sensorTypeCached = sensorType
+ val isForward = nav.forward
+ val currStep = nav.currStep
+ val theClass: Class<Fragment>? =
+ when (currStep) {
+ Confirmation -> FingerprintEnrollConfirmationV2Fragment::class.java as Class<Fragment>
+ Education -> FingerprintEnrollFindSensorV2Fragment::class.java as Class<Fragment>
+ is Enrollment -> {
+ when (sensorType) {
+ FingerprintSensorType.REAR -> RFPSEnrollFragment::class.java as Class<Fragment>
+ else -> FingerprintEnrollEnrollingV2Fragment::class.java as Class<Fragment>
+ }
+ }
+ Intro -> FingerprintEnrollIntroV2Fragment::class.java as Class<Fragment>
+ else -> null
}
- } else if (currStep == LaunchConfirmDeviceCredential) {
- launchConfirmOrChooseLock(userId)
+
+ if (theClass != null) {
+ supportFragmentManager.fragments.onEach { fragment ->
+ supportFragmentManager.beginTransaction().remove(fragment).commit()
+ }
+
+ supportFragmentManager
+ .beginTransaction()
+ .setReorderingAllowed(true)
+ .add(R.id.fragment_container_view, theClass, null)
+ .commit()
+ } else {
+
+ if (currStep is Finish) {
+ if (currStep.resultCode != null) {
+ finishActivity(currStep.resultCode)
+ } else {
+ finish()
+ }
+ } else if (currStep == LaunchConfirmDeviceCredential) {
+ launchConfirmOrChooseLock(userId)
+ }
}
}
- }
}
val fromSettingsSummary =
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt
index 0afa613..bfd4264 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt
@@ -30,6 +30,7 @@
import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog
import com.android.settings.biometrics.fingerprint.FingerprintFindSensorAnimation
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.google.android.setupcompat.template.FooterBarMixin
import com.google.android.setupcompat.template.FooterButton
@@ -54,23 +55,8 @@
private var animation: FingerprintFindSensorAnimation? = null
private var contentLayoutId: Int = -1
- private lateinit var viewModel: FingerprintEnrollFindSensorViewModel
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- viewModel =
- ViewModelProvider(requireActivity())[FingerprintEnrollFindSensorViewModel::class.java]
- lifecycleScope.launch {
- viewModel.sensorType.collect {
- contentLayoutId =
- when (it) {
- FingerprintSensorType.UDFPS_OPTICAL,
- FingerprintSensorType.UDFPS_ULTRASONIC -> R.layout.udfps_enroll_find_sensor_layout
- FingerprintSensorType.POWER_BUTTON -> R.layout.sfps_enroll_find_sensor_layout
- else -> R.layout.fingerprint_v2_enroll_find_sensor
- }
- }
- }
+ private val viewModel: FingerprintEnrollFindSensorViewModel by lazy {
+ ViewModelProvider(requireActivity())[FingerprintEnrollFindSensorViewModel::class.java]
}
override fun onCreateView(
@@ -78,6 +64,18 @@
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
+
+ val sensorType =
+ ViewModelProvider(requireActivity())[FingerprintEnrollViewModel::class.java].sensorTypeCached
+
+ contentLayoutId =
+ when (sensorType) {
+ FingerprintSensorType.UDFPS_OPTICAL,
+ FingerprintSensorType.UDFPS_ULTRASONIC -> R.layout.udfps_enroll_find_sensor_layout
+ FingerprintSensorType.POWER_BUTTON -> R.layout.sfps_enroll_find_sensor_layout
+ else -> R.layout.fingerprint_v2_enroll_find_sensor
+ }
+
return inflater.inflate(contentLayoutId, container, false).also { it ->
val view = it!! as GlifLayout
@@ -106,7 +104,8 @@
}
lifecycleScope.launch {
viewModel.showRfpsAnimation.collect {
- animation = view.findViewById(R.id.fingerprint_sensor_location_animation)
+ animation =
+ view.findViewById(R.id.fingerprint_sensor_location_animation)
animation!!.startAnimation()
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt
index 898b158..b1ab301 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt
@@ -36,11 +36,11 @@
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.android.settings.R
+import com.android.settings.biometrics.fingerprint2.shared.model.Unicorn
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Unicorn
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.google.android.setupcompat.template.FooterBarMixin
import com.google.android.setupcompat.template.FooterButton
@@ -120,7 +120,7 @@
viewLifecycleOwner.lifecycleScope.launch {
combine(
- navigationViewModel.enrollType,
+ navigationViewModel.fingerprintFlow,
fingerprintViewModel.sensorType,
) { enrollType, sensorType ->
Pair(enrollType, sensorType)
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/README.md b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/README.md
new file mode 100644
index 0000000..dfb9598
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/README.md
@@ -0,0 +1,26 @@
+# Module enrollment
+
+### Fingerprint Settings Enrollment Modules
+
+This directory is responsible for containing the enrollment modules, each enrollment module is
+responsible for the actual enrolling portion of FingerprintEnrollment.
+The modules should be split out into udfps, rfps, and sfps.
+
+[comment]: <> This file structure print out has been generated with the tree command.
+
+```
+├── enrolling
+│ └── rfps
+│ ├── data
+│ ├── domain
+│ │ └── RFPSInteractor.kt
+│ ├── README.md
+│ └── ui
+│ ├── fragment
+│ │ └── RFPSEnrollFragment.kt
+│ ├── viewmodel
+│ │ └── RFPSViewModel.kt
+│ └── widget
+│ └── RFPSProgressIndicator.kt
+└── README.md
+```
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/fragment/RFPSEnrollFragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/fragment/RFPSEnrollFragment.kt
new file mode 100644
index 0000000..d8c2f5a
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/fragment/RFPSEnrollFragment.kt
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.fragment
+
+import android.graphics.Color
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.AnimationUtils
+import android.view.animation.Interpolator
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.settings.R
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSIconTouchViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.FingerprintErrorDialog
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.IconTouchDialog
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.RFPSProgressBar
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.OrientationStateViewModel
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment
+import com.google.android.setupcompat.template.FooterBarMixin
+import com.google.android.setupcompat.template.FooterButton
+import com.google.android.setupdesign.GlifLayout
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.launch
+
+private const val TAG = "RFPSEnrollFragment"
+
+/** This fragment is responsible for taking care of rear fingerprint enrollment. */
+class RFPSEnrollFragment : Fragment(R.layout.fingerprint_v2_rfps_enroll_enrolling) {
+
+ private lateinit var linearOutSlowInInterpolator: Interpolator
+ private lateinit var fastOutLinearInInterpolator: Interpolator
+ private lateinit var textView: TextView
+ private lateinit var progressBar: RFPSProgressBar
+
+ private val iconTouchViewModel: RFPSIconTouchViewModel by lazy {
+ ViewModelProvider(requireActivity())[RFPSIconTouchViewModel::class.java]
+ }
+
+ private val orientationViewModel: OrientationStateViewModel by lazy {
+ ViewModelProvider(requireActivity())[OrientationStateViewModel::class.java]
+ }
+
+ private val rfpsViewModel: RFPSViewModel by lazy {
+ ViewModelProvider(requireActivity())[RFPSViewModel::class.java]
+ }
+
+ private val backgroundViewModel: BackgroundViewModel by lazy {
+ ViewModelProvider(requireActivity())[BackgroundViewModel::class.java]
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val view = super.onCreateView(inflater, container, savedInstanceState)!!
+ val fragment = this
+ val context = requireContext()
+ val glifLayout = view.requireViewById(R.id.setup_wizard_layout) as GlifLayout
+ glifLayout.setDescriptionText(R.string.security_settings_fingerprint_enroll_start_message)
+ glifLayout.setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title)
+
+ fastOutLinearInInterpolator =
+ AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_linear_in)
+ linearOutSlowInInterpolator =
+ AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in)
+
+ textView = view.requireViewById(R.id.text) as TextView
+ progressBar = view.requireViewById(R.id.fingerprint_progress_bar) as RFPSProgressBar
+
+ val footerBarMixin = glifLayout.getMixin(FooterBarMixin::class.java)
+ footerBarMixin.secondaryButton =
+ FooterButton.Builder(context)
+ .setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
+ .setListener { Log.e(TAG, "skip enrollment!") }
+ .setButtonType(FooterButton.ButtonType.SKIP)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
+ .build()
+ footerBarMixin.buttonContainer.setBackgroundColor(Color.TRANSPARENT)
+
+ progressBar.setOnTouchListener { _, motionEvent ->
+ if (motionEvent.actionMasked == MotionEvent.ACTION_DOWN) {
+ iconTouchViewModel.userTouchedFingerprintIcon()
+ }
+ true
+ }
+
+ // On any orientation event, dismiss dialogs.
+ viewLifecycleOwner.lifecycleScope.launch {
+ orientationViewModel.orientation.collect { dismissDialogs() }
+ }
+
+ // Signal we are ready for enrollment.
+ rfpsViewModel.readyForEnrollment()
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ // Icon animation update
+ viewLifecycleOwner.lifecycleScope.launch {
+ rfpsViewModel.shouldAnimateIcon.collect { animate ->
+ progressBar.updateIconAnimation(animate)
+ }
+ }
+
+ // Flow to show a dialog.
+ viewLifecycleOwner.lifecycleScope.launch {
+ iconTouchViewModel.shouldShowDialog.collectLatest { showDialog ->
+ if (showDialog) {
+ try {
+ IconTouchDialog.showInstance(fragment)
+ } catch (exception: Exception) {
+ Log.d(TAG, "Dialog dismissed due to $exception")
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // If we go to the background, then finish enrollment. This should be permanent finish,
+ // and shouldn't be reset until we explicitly tell the view model we want to retry
+ // enrollment.
+ viewLifecycleOwner.lifecycleScope.launch {
+ backgroundViewModel.background
+ .filter { inBackground -> inBackground }
+ .collect { rfpsViewModel.stopEnrollment() }
+ }
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ rfpsViewModel.progress.filterNotNull().collect { progress -> handleEnrollProgress(progress) }
+ }
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ rfpsViewModel.helpMessage.filterNotNull().collect { help ->
+ textView.text = help.helpString
+ textView.visibility = View.VISIBLE
+ textView.translationY =
+ resources.getDimensionPixelSize(R.dimen.fingerprint_error_text_appear_distance).toFloat()
+ textView.alpha = 0f
+ textView
+ .animate()
+ .alpha(1f)
+ .translationY(0f)
+ .setDuration(200)
+ .setInterpolator(linearOutSlowInInterpolator)
+ .start()
+
+ }
+ }
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ rfpsViewModel.errorMessage.filterNotNull().collect { error -> handleEnrollError(error) }
+ }
+ viewLifecycleOwner.lifecycleScope.launch {
+ rfpsViewModel.textViewIsVisible.collect {
+ textView.visibility = if (it) View.VISIBLE else View.INVISIBLE
+ }
+ }
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ rfpsViewModel.clearHelpMessage.collect {
+ textView
+ .animate()
+ .alpha(0f)
+ .translationY(
+ resources
+ .getDimensionPixelSize(R.dimen.fingerprint_error_text_disappear_distance)
+ .toFloat()
+ )
+ .setDuration(100)
+ .setInterpolator(fastOutLinearInInterpolator)
+ .withEndAction { rfpsViewModel.setVisibility(false) }
+ .start()
+ }
+ }
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.DESTROYED) {
+ rfpsViewModel.stopEnrollment()
+ dismissDialogs()
+ }
+ }
+ return view
+ }
+
+ private fun handleEnrollError(error: FingerEnrollState.EnrollError) {
+ val fragment = this
+ viewLifecycleOwner.lifecycleScope.launch {
+ try {
+ val shouldRestartEnrollment = FingerprintErrorDialog.showInstance(error, fragment)
+ } catch (exception: Exception) {
+ Log.e(TAG, "Exception occurred $exception")
+ }
+ onEnrollmentFailed()
+ }
+ }
+
+ private fun onEnrollmentFailed() {
+ rfpsViewModel.stopEnrollment()
+ }
+
+ private fun handleEnrollProgress(progress: FingerEnrollState.EnrollProgress) {
+ progressBar.updateProgress(
+ progress.remainingSteps.toFloat() / progress.totalStepsRequired.toFloat()
+ )
+
+ if (progress.remainingSteps == 0) {
+ performNextStepSuccess()
+ }
+ }
+
+ private fun performNextStepSuccess() {}
+
+ private fun dismissDialogs() {
+ val transaction = parentFragmentManager.beginTransaction()
+ for (frag in parentFragmentManager.fragments) {
+ if (frag is InstrumentedDialogFragment) {
+ Log.d(TAG, "removing dialog settings fragment $frag")
+ frag.dismiss()
+ transaction.remove(frag)
+ }
+ }
+ transaction.commitAllowingStateLoss()
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSIconTouchViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSIconTouchViewModel.kt
new file mode 100644
index 0000000..c16e65c
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSIconTouchViewModel.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.transform
+import kotlinx.coroutines.flow.update
+
+private const val touchesToShowDialog = 3
+/**
+ * This class is responsible for counting the number of touches on the fingerprint icon, and if this
+ * number reaches a threshold it will produce an action via [shouldShowDialog] to indicate the ui
+ * should show a dialog.
+ */
+class RFPSIconTouchViewModel : ViewModel() {
+
+ /** Keeps the number of times a user has touches the fingerprint icon. */
+ private val _touches: MutableStateFlow<Int> = MutableStateFlow(0)
+
+ /**
+ * Whether or not the UI should be showing the dialog. By making this SharingStarted.Eagerly
+ * the first event 0 % 3 == 0 will fire as soon as this view model is created, so it should
+ * be ignored and work as intended.
+ */
+ val shouldShowDialog: Flow<Boolean> =
+ _touches
+ .transform { numTouches -> emit((numTouches % touchesToShowDialog) == 0) }
+ .shareIn(viewModelScope, SharingStarted.Eagerly, 0)
+
+ /** Indicates a user has tapped on the fingerprint icon. */
+ fun userTouchedFingerprintIcon() {
+ _touches.update { _touches.value + 1 }
+ }
+
+ class RFPSIconTouchViewModelFactory : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel> create(modelClass: Class<T>): T {
+ return RFPSIconTouchViewModel() as T
+ }
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt
new file mode 100644
index 0000000..58d604e
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.transform
+import kotlinx.coroutines.flow.update
+
+/** View Model used by the rear fingerprint enrollment fragment. */
+class RFPSViewModel(
+ private val fingerprintEnrollViewModel: FingerprintEnrollEnrollingViewModel,
+) : ViewModel() {
+
+ /** Value to indicate if the text view is visible or not **/
+ private val _textViewIsVisible = MutableStateFlow<Boolean>(false)
+ val textViewIsVisible: Flow<Boolean> = _textViewIsVisible.asStateFlow()
+
+ /** Indicates if the icon should be animating or not */
+ val shouldAnimateIcon = fingerprintEnrollViewModel.enrollFlowShouldBeRunning
+
+ private val enrollFlow: Flow<FingerEnrollState?> = fingerprintEnrollViewModel.enrollFLow
+
+ /**
+ * Enroll progress message with a replay of size 1 allowing for new subscribers to get the most
+ * recent state (this is useful for things like screen rotation)
+ */
+ val progress: Flow<FingerEnrollState.EnrollProgress?> =
+ enrollFlow
+ .filterIsInstance<FingerEnrollState.EnrollProgress>()
+ .shareIn(viewModelScope, SharingStarted.Eagerly, 1)
+
+ /** Clear help message on enroll progress */
+ val clearHelpMessage: Flow<Boolean> = progress.map { it != null }
+
+ /** Enroll help message that is only displayed once */
+ val helpMessage: Flow<FingerEnrollState.EnrollHelp?> =
+ enrollFlow
+ .filterIsInstance<FingerEnrollState.EnrollHelp>()
+ .shareIn(viewModelScope, SharingStarted.Eagerly, 0).transform {
+ _textViewIsVisible.update { true }
+ }
+
+ /**
+ * The error message should only be shown once, for scenarios like screen rotations, we don't want
+ * to re-show the error message.
+ */
+ val errorMessage: Flow<FingerEnrollState.EnrollError?> =
+ enrollFlow
+ .filterIsInstance<FingerEnrollState.EnrollError>()
+ .shareIn(viewModelScope, SharingStarted.Eagerly, 0)
+
+ /** Indicates if the consumer is ready for enrollment */
+ fun readyForEnrollment() {
+ fingerprintEnrollViewModel.canEnroll()
+ }
+
+ /** Indicates if enrollment should stop */
+ fun stopEnrollment() {
+ fingerprintEnrollViewModel.stopEnroll()
+ }
+
+ fun setVisibility(isVisible: Boolean) {
+ _textViewIsVisible.update { isVisible }
+ }
+
+ class RFPSViewModelFactory(
+ private val fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel,
+ ) : ViewModelProvider.Factory {
+
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel> create(
+ modelClass: Class<T>,
+ ): T {
+ return RFPSViewModel(fingerprintEnrollEnrollingViewModel) as T
+ }
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/FingerprintErrorDialog.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/FingerprintErrorDialog.kt
new file mode 100644
index 0000000..b9c628e
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/FingerprintErrorDialog.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget
+
+import android.app.AlertDialog
+import android.app.Dialog
+import android.app.settings.SettingsEnums
+import android.content.DialogInterface
+import android.os.Bundle
+import android.util.Log
+import androidx.fragment.app.Fragment
+import com.android.settings.R
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment
+import kotlin.coroutines.resume
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+private const val TAG = "FingerprintErrorDialog"
+
+/** A Dialog used for fingerprint enrollment when an error occurs. */
+class FingerprintErrorDialog : InstrumentedDialogFragment() {
+ private lateinit var onContinue: DialogInterface.OnClickListener
+ private lateinit var onTryAgain: DialogInterface.OnClickListener
+ private lateinit var onCancelListener: DialogInterface.OnCancelListener
+
+ override fun onCancel(dialog: DialogInterface) {
+ Log.d(TAG, "onCancel $dialog")
+ onCancelListener.onCancel(dialog)
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ Log.d(TAG, "onCreateDialog $this")
+ val errorString = requireArguments().getInt(KEY_MESSAGE)
+ val errorTitle = requireArguments().getInt(KEY_TITLE)
+ val builder = AlertDialog.Builder(requireContext())
+ val shouldShowTryAgain = requireArguments().getBoolean(KEY_SHOULD_TRY_AGAIN)
+ builder.setTitle(errorTitle).setMessage(errorString).setCancelable(false)
+
+ if (shouldShowTryAgain) {
+ builder
+ .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_try_again) {
+ dialog,
+ which ->
+ dialog.dismiss()
+ onTryAgain.onClick(dialog, which)
+ }
+ .setNegativeButton(R.string.security_settings_fingerprint_enroll_dialog_ok) { dialog, which
+ ->
+ dialog.dismiss()
+ onContinue.onClick(dialog, which)
+ }
+ } else {
+ builder.setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok) {
+ dialog,
+ which ->
+ dialog.dismiss()
+ onContinue.onClick(dialog, which)
+ }
+ }
+
+ val dialog = builder.create()
+ dialog.setCanceledOnTouchOutside(false)
+ return dialog
+ }
+
+ override fun getMetricsCategory(): Int {
+ return SettingsEnums.DIALOG_FINGERPINT_ERROR
+ }
+
+ companion object {
+ private const val KEY_MESSAGE = "fingerprint_message"
+ private const val KEY_TITLE = "fingerprint_title"
+ private const val KEY_SHOULD_TRY_AGAIN = "should_try_again"
+
+ suspend fun showInstance(
+ error: FingerEnrollState.EnrollError,
+ fragment: Fragment,
+ ) = suspendCancellableCoroutine { continuation ->
+ val dialog = FingerprintErrorDialog()
+ dialog.onTryAgain = DialogInterface.OnClickListener { _, _ -> continuation.resume(true) }
+
+ dialog.onContinue = DialogInterface.OnClickListener { _, _ -> continuation.resume(false) }
+
+ dialog.onCancelListener =
+ DialogInterface.OnCancelListener {
+ Log.d(TAG, "onCancelListener clicked $dialog")
+ continuation.resume(null)
+ }
+
+ continuation.invokeOnCancellation { Log.d(TAG, "invokeOnCancellation $dialog") }
+
+ val bundle = Bundle()
+ bundle.putInt(
+ KEY_TITLE,
+ error.errTitle,
+ )
+ bundle.putInt(
+ KEY_MESSAGE,
+ error.errString,
+ )
+ bundle.putBoolean(
+ KEY_SHOULD_TRY_AGAIN,
+ error.shouldRetryEnrollment,
+ )
+ dialog.arguments = bundle
+ Log.d(TAG, "showing dialog $dialog")
+ dialog.show(fragment.parentFragmentManager, FingerprintErrorDialog::class.java.toString())
+ }
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/IconTouchDialog.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/IconTouchDialog.kt
new file mode 100644
index 0000000..c086343
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/IconTouchDialog.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget
+
+import android.app.AlertDialog
+import android.app.Dialog
+import android.app.settings.SettingsEnums
+import android.content.DialogInterface
+import android.os.Bundle
+import android.util.Log
+import androidx.fragment.app.Fragment
+import com.android.settings.R
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment
+import kotlin.coroutines.resume
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+private const val TAG = "IconTouchDialog"
+
+/** Dialog shown when the user taps the Progress bar a certain amount of times. */
+class IconTouchDialog : InstrumentedDialogFragment() {
+ lateinit var onDismissListener: DialogInterface.OnClickListener
+ lateinit var onCancelListener: DialogInterface.OnCancelListener
+
+ override fun onCancel(dialog: DialogInterface) {
+ Log.d(TAG, "onCancel $dialog")
+ onCancelListener.onCancel(dialog)
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val builder: AlertDialog.Builder = AlertDialog.Builder(activity, R.style.Theme_AlertDialog)
+ builder
+ .setTitle(R.string.security_settings_fingerprint_enroll_touch_dialog_title)
+ .setMessage(R.string.security_settings_fingerprint_enroll_touch_dialog_message)
+ .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok) { dialog, which ->
+ dialog.dismiss()
+ onDismissListener.onClick(dialog, which)
+ }
+ .setOnCancelListener { onCancelListener.onCancel(it) }
+ return builder.create()
+ }
+
+ override fun getMetricsCategory(): Int {
+ return SettingsEnums.DIALOG_FINGERPRINT_ICON_TOUCH
+ }
+
+ companion object {
+ suspend fun showInstance(fragment: Fragment) = suspendCancellableCoroutine { continuation ->
+ val dialog = IconTouchDialog()
+ dialog.onDismissListener =
+ DialogInterface.OnClickListener { _, _ -> continuation.resume("Done") }
+ dialog.onCancelListener =
+ DialogInterface.OnCancelListener { _ -> continuation.resume("OnCancel") }
+
+ continuation.invokeOnCancellation { Log.d(TAG, "invokeOnCancellation $dialog") }
+
+ dialog.show(fragment.parentFragmentManager, IconTouchDialog::class.java.toString())
+ }
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/RFPSProgressBar.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/RFPSProgressBar.kt
new file mode 100644
index 0000000..fe62681
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/RFPSProgressBar.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget
+
+import android.animation.ObjectAnimator
+import android.content.Context
+import android.graphics.PorterDuff
+import android.graphics.drawable.Animatable2
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.LayerDrawable
+import android.util.AttributeSet
+import android.view.animation.AnimationUtils
+import android.view.animation.Interpolator
+import com.android.settings.R
+import com.android.settings.widget.RingProgressBar
+
+/** Progress bar for rear fingerprint enrollment. */
+class RFPSProgressBar(context: Context, attributeSet: AttributeSet) :
+ RingProgressBar(context, attributeSet) {
+
+ private val fastOutSlowInInterpolator: Interpolator
+
+ private val iconAnimationDrawable: AnimatedVectorDrawable
+ private val iconBackgroundBlinksDrawable: AnimatedVectorDrawable
+
+ private val maxProgress: Int
+
+ private var progressAnimation: ObjectAnimator? = null
+
+ private var shouldAnimateInternal: Boolean = true
+
+ init {
+ val fingerprintDrawable = background as LayerDrawable
+ iconAnimationDrawable =
+ fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_animation)
+ as AnimatedVectorDrawable
+ iconBackgroundBlinksDrawable =
+ fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_background)
+ as AnimatedVectorDrawable
+
+ fastOutSlowInInterpolator =
+ AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in)
+
+ iconAnimationDrawable.registerAnimationCallback(
+ object : Animatable2.AnimationCallback() {
+ override fun onAnimationEnd(drawable: Drawable?) {
+ super.onAnimationEnd(drawable)
+ if (shouldAnimateInternal) {
+ animateIconAnimationInternal()
+ }
+ }
+ }
+ )
+ animateIconAnimationInternal()
+
+ progressBackgroundTintMode = PorterDuff.Mode.SRC
+
+ val attributes =
+ context.obtainStyledAttributes(R.style.RingProgressBarStyle, intArrayOf(android.R.attr.max))
+
+ maxProgress = attributes.getInt(0, -1)
+
+ attributes.recycle()
+ }
+
+ /** Indicates if the progress animation should be running */
+ fun updateIconAnimation(shouldAnimate: Boolean) {
+ if (shouldAnimate && !shouldAnimateInternal) {
+ animateIconAnimationInternal()
+ }
+
+ shouldAnimateInternal = shouldAnimate
+ }
+
+ /** This function should only be called when actual progress has been made. */
+ fun updateProgress(percentComplete: Float) {
+ val progress = maxProgress - (percentComplete.coerceIn(0.0f, 100.0f) * maxProgress).toInt()
+ iconBackgroundBlinksDrawable.start()
+
+ progressAnimation?.isRunning?.let { progressAnimation!!.cancel() }
+
+ progressAnimation = ObjectAnimator.ofInt(this, "progress", getProgress(), progress)
+
+ progressAnimation?.interpolator = fastOutSlowInInterpolator
+ progressAnimation?.setDuration(250)
+ progressAnimation?.start()
+ }
+
+ private fun animateIconAnimationInternal() {
+ iconAnimationDrawable.start()
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/BackgroundViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/BackgroundViewModel.kt
new file mode 100644
index 0000000..2b53a53
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/BackgroundViewModel.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+/** A class for determining if the application is in the background or not. */
+class BackgroundViewModel : ViewModel() {
+
+ private val _background = MutableStateFlow(false)
+ /** When true, the application is in background, else false */
+ val background = _background.asStateFlow()
+
+ /** Indicates that the application has been put in the background. */
+ fun wentToBackground() {
+ _background.update { true }
+ }
+
+ /** Indicates that the application has been brought to the foreground. */
+ fun inForeground() {
+ _background.update { false }
+ }
+
+ class BackgroundViewModelFactory : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel> create(modelClass: Class<T>): T {
+ return BackgroundViewModel() as T
+ }
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt
new file mode 100644
index 0000000..7ab315e
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.transformLatest
+import kotlinx.coroutines.flow.update
+
+/**
+ * This class is a wrapper around the [FingerprintEnrollViewModel] and decides when
+ * the user should or should not be enrolling.
+ */
+class FingerprintEnrollEnrollingViewModel(
+ private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
+ backgroundViewModel: BackgroundViewModel,
+) : ViewModel() {
+
+ private val _didTryEnrollment = MutableStateFlow(false)
+ private val _userDidEnroll = MutableStateFlow(false)
+ /** Indicates if the enrollment flow should be running. */
+ val enrollFlowShouldBeRunning: Flow<Boolean> =
+ _userDidEnroll.combine(backgroundViewModel.background) { shouldEnroll, isInBackground ->
+ if (isInBackground) {
+ false
+ } else {
+ shouldEnroll
+ }
+ }
+
+ /**
+ * Used to indicate the consumer of the view model is ready for an enrollment. Note that this does
+ * not necessarily try an enrollment.
+ */
+ fun canEnroll() {
+ // Update _consumerShouldEnroll after updating the other values.
+ if (!_didTryEnrollment.value) {
+ _didTryEnrollment.update { true }
+ _userDidEnroll.update { true }
+ }
+ }
+
+ /** Used to indicate to stop the enrollment. */
+ fun stopEnroll() {
+ _userDidEnroll.update { false }
+ }
+
+ /** Collects the enrollment flow based on [enrollFlowShouldBeRunning] */
+ val enrollFLow =
+ enrollFlowShouldBeRunning.transformLatest {
+ if (it) {
+ fingerprintEnrollViewModel.enrollFlow.collect { event -> emit(event) }
+ }
+ }
+
+ class FingerprintEnrollEnrollingViewModelFactory(
+ private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
+ private val backgroundViewModel: BackgroundViewModel
+ ) : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel> create(
+ modelClass: Class<T>,
+ ): T {
+ return FingerprintEnrollEnrollingViewModel(fingerprintEnrollViewModel, backgroundViewModel)
+ as T
+ }
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt
index 90aefc8..7722a46 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt
@@ -16,12 +16,11 @@
package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
-import android.hardware.fingerprint.FingerprintManager
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
-import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.shared.model.SetupWizard
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -40,6 +39,7 @@
private val navigationViewModel: FingerprintEnrollNavigationViewModel,
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
+ backgroundViewModel: BackgroundViewModel,
accessibilityViewModel: AccessibilityViewModel,
foldStateViewModel: FoldStateViewModel,
orientationStateViewModel: OrientationStateViewModel
@@ -88,6 +88,14 @@
/** Represents the stream of showing error dialog. */
val showErrorDialog = _showErrorDialog.filterNotNull()
+ private var _didTryEducation = false
+ private var _education: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ /** Indicates if the education flow should be running. */
+ private val educationFlowShouldBeRunning: Flow<Boolean> =
+ _education.combine(backgroundViewModel.background) { shouldRunEducation, isInBackground ->
+ !isInBackground && shouldRunEducation
+ }
+
init {
// Start or end enroll flow
viewModelScope.launch {
@@ -107,40 +115,58 @@
}
.collect { token ->
if (token != null) {
- fingerprintEnrollViewModel.startEnroll(token, EnrollReason.FindSensor)
+ canStartEducation()
} else {
- fingerprintEnrollViewModel.stopEnroll()
+ stopEducation()
}
}
}
// Enroll progress flow
viewModelScope.launch {
- combine(
- navigationViewModel.enrollType,
- fingerprintEnrollViewModel.enrollFlow.filterNotNull()
- ) { enrollType, enrollFlow ->
- Pair(enrollType, enrollFlow)
- }
- .collect { (enrollType, enrollFlow) ->
- when (enrollFlow) {
- // TODO: Cancel the enroll() when EnrollProgress is received instead of proceeding to
- // Enrolling page. Otherwise Enrolling page will receive the EnrollError.
- is FingerEnrollStateViewModel.EnrollProgress -> proceedToEnrolling()
- is FingerEnrollStateViewModel.EnrollError -> {
- val errMsgId = enrollFlow.errMsgId
- if (errMsgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
- proceedToEnrolling()
- } else {
- _showErrorDialog.update { Pair(errMsgId, enrollType == SetupWizard) }
+ educationFlowShouldBeRunning.collect {
+ // Only collect the flow when we should be running.
+ if (it) {
+ combine(
+ navigationViewModel.fingerprintFlow,
+ fingerprintEnrollViewModel.educationEnrollFlow.filterNotNull(),
+ ) { enrollType, educationFlow ->
+ Pair(enrollType, educationFlow)
+ }
+ .collect { (enrollType, educationFlow) ->
+ when (educationFlow) {
+ // TODO: Cancel the enroll() when EnrollProgress is received instead of proceeding
+ // to
+ // Enrolling page. Otherwise Enrolling page will receive the EnrollError.
+ is FingerEnrollState.EnrollProgress -> proceedToEnrolling()
+ is FingerEnrollState.EnrollError -> {
+ if (educationFlow.isCancelled) {
+ proceedToEnrolling()
+ } else {
+ _showErrorDialog.update { Pair(educationFlow.errString, enrollType == SetupWizard) }
+ }
+ }
+ is FingerEnrollState.EnrollHelp -> {}
}
}
- is FingerEnrollStateViewModel.EnrollHelp -> {}
- }
}
+ }
}
}
+ /** Indicates if education can begin */
+ private fun canStartEducation() {
+ if (!_didTryEducation) {
+ _didTryEducation = true
+ _education.update { true }
+ }
+ }
+
+ /** Indicates that education has finished */
+ private fun stopEducation() {
+ _education.update { false }
+ }
+
/** Proceed to EnrollEnrolling page. */
fun proceedToEnrolling() {
navigationViewModel.nextStep()
@@ -150,6 +176,7 @@
private val navigationViewModel: FingerprintEnrollNavigationViewModel,
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
+ private val backgroundViewModel: BackgroundViewModel,
private val accessibilityViewModel: AccessibilityViewModel,
private val foldStateViewModel: FoldStateViewModel,
private val orientationStateViewModel: OrientationStateViewModel
@@ -160,6 +187,7 @@
navigationViewModel,
fingerprintEnrollViewModel,
gatekeeperViewModel,
+ backgroundViewModel,
accessibilityViewModel,
foldStateViewModel,
orientationStateViewModel
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt
index 392d205..c7a1071 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt
@@ -17,32 +17,41 @@
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.transformLatest
-import kotlinx.coroutines.flow.update
-
-private const val TAG = "FingerprintEnrollViewModel"
/** Represents all of the fingerprint information needed for a fingerprint enrollment process. */
class FingerprintEnrollViewModel(
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
- backgroundDispatcher: CoroutineDispatcher,
+ gatekeeperViewModel: FingerprintGatekeeperViewModel,
+ navigationViewModel: FingerprintEnrollNavigationViewModel,
) : ViewModel() {
- private var _enrollReason: MutableStateFlow<EnrollReason> =
- MutableStateFlow(EnrollReason.FindSensor)
- private var _hardwareAuthToken: MutableStateFlow<ByteArray?> = MutableStateFlow(null)
- private var _consumerShouldEnroll: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ /**
+ * Cached value of [FingerprintSensorType]
+ *
+ * This is typically used by fragments that change their layout/behavior based on this
+ * information. This value should be set before any fragment is created.
+ */
+ var sensorTypeCached: FingerprintSensorType? = null
+ private var _enrollReason: Flow<EnrollReason?> =
+ navigationViewModel.navigationViewModel.map {
+ when (it.currStep) {
+ is Enrollment -> EnrollReason.EnrollEnrolling
+ is Education -> EnrollReason.FindSensor
+ else -> null
+ }
+ }
/** Represents the stream of [FingerprintSensorType] */
val sensorType: Flow<FingerprintSensorType> =
@@ -51,47 +60,68 @@
/**
* A flow that contains a [FingerprintEnrollViewModel] which contains the relevant information for
* an enrollment process
+ *
+ * This flow should be the only flow which calls enroll().
*/
- val enrollFlow: Flow<FingerEnrollStateViewModel> =
- combine(_consumerShouldEnroll, _hardwareAuthToken, _enrollReason) {
- consumerShouldEnroll,
- hardwareAuthToken,
- enrollReason ->
- Triple(consumerShouldEnroll, hardwareAuthToken, enrollReason)
+ val _enrollFlow: Flow<FingerEnrollState> =
+ combine(gatekeeperViewModel.gatekeeperInfo, _enrollReason) { hardwareAuthToken, enrollReason,
+ ->
+ Pair(hardwareAuthToken, enrollReason)
}
.transformLatest {
- // transformLatest() instead of transform() is used here for cancelling previous enroll()
- // whenever |consumerShouldEnroll| is changed. Otherwise the latest value will be suspended
- // since enroll() is an infinite callback flow.
- (consumerShouldEnroll, hardwareAuthToken, enrollReason) ->
- if (consumerShouldEnroll && hardwareAuthToken != null) {
- fingerprintManagerInteractor.enroll(hardwareAuthToken, enrollReason).collect { emit(it) }
+ /** [transformLatest] is used as we want to make sure to cancel previous API call. */
+ (hardwareAuthToken, enrollReason) ->
+ if (hardwareAuthToken is GatekeeperInfo.GatekeeperPasswordInfo && enrollReason != null) {
+ fingerprintManagerInteractor.enroll(hardwareAuthToken.token, enrollReason).collect {
+ emit(it)
+ }
}
}
- .flowOn(backgroundDispatcher)
+ .shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 0)
- /** Used to indicate the consumer of the view model is ready for an enrollment. */
- fun startEnroll(hardwareAuthToken: ByteArray?, enrollReason: EnrollReason) {
- _enrollReason.update { enrollReason }
- _hardwareAuthToken.update { hardwareAuthToken }
- // Update _consumerShouldEnroll after updating the other values.
- _consumerShouldEnroll.update { true }
- }
+ /**
+ * This flow will kick off education when
+ * 1) There is an active subscriber to this flow
+ * 2) shouldEnroll is true and we are on the FindSensor step
+ */
+ val educationEnrollFlow: Flow<FingerEnrollState?> =
+ _enrollReason.filterNotNull().transformLatest { enrollReason ->
+ if (enrollReason == EnrollReason.FindSensor) {
+ _enrollFlow.collect { event -> emit(event) }
+ } else {
+ emit(null)
+ }
+ }
- /** Used to indicate to stop the enrollment. */
- fun stopEnroll() {
- _consumerShouldEnroll.update { false }
- }
+ /**
+ * This flow will kick off enrollment when
+ * 1) There is an active subscriber to this flow
+ * 2) shouldEnroll is true and we are on the EnrollEnrolling step
+ */
+ val enrollFlow: Flow<FingerEnrollState?> =
+ _enrollReason.filterNotNull().transformLatest { enrollReason ->
+ if (enrollReason == EnrollReason.EnrollEnrolling) {
+ _enrollFlow.collect { event -> emit(event) }
+ } else {
+ emit(null)
+ }
+ }
class FingerprintEnrollViewModelFactory(
val interactor: FingerprintManagerInteractor,
- val backgroundDispatcher: CoroutineDispatcher
+ val gatekeeperViewModel: FingerprintGatekeeperViewModel,
+ val navigationViewModel: FingerprintEnrollNavigationViewModel,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
modelClass: Class<T>,
): T {
- return FingerprintEnrollViewModel(interactor, backgroundDispatcher) as T
+ return FingerprintEnrollViewModel(
+ interactor,
+ gatekeeperViewModel,
+ navigationViewModel,
+ )
+ as T
}
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrolllNavigationViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrolllNavigationViewModel.kt
index 97c8271..2e5dce0 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrolllNavigationViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrolllNavigationViewModel.kt
@@ -21,31 +21,20 @@
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintFlow
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
private const val TAG = "FingerprintEnrollNavigationViewModel"
/**
- * The [EnrollType] for fingerprint enrollment indicates information on how the flow should behave.
- */
-sealed class EnrollType
-
-/** The default enrollment experience, typically called from Settings */
-object Default : EnrollType()
-
-/** SetupWizard/Out of box experience (OOBE) enrollment type. */
-object SetupWizard : EnrollType()
-
-/** Unicorn enrollment type */
-object Unicorn : EnrollType()
-
-/**
* This class is responsible for sending a [NavigationStep] which indicates where the user is in the
* Fingerprint Enrollment flow
*/
@@ -53,31 +42,26 @@
private val dispatcher: CoroutineDispatcher,
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
- private val canSkipConfirm: Boolean
+ private val firstStep: NextStepViewModel,
+ private val navState: NavState,
+ private val theFingerprintFlow: FingerprintFlow,
) : ViewModel() {
private class InternalNavigationStep(
lastStep: NextStepViewModel,
nextStep: NextStepViewModel,
forward: Boolean,
- var canNavigate: Boolean
+ var canNavigate: Boolean,
) : NavigationStep(lastStep, nextStep, forward)
- private var _enrollType = MutableStateFlow<EnrollType?>(Default)
+ private var _fingerprintFlow = MutableStateFlow<FingerprintFlow?>(theFingerprintFlow)
- /** A flow that indicates the [EnrollType] */
- val enrollType: Flow<EnrollType?> = _enrollType.asStateFlow()
-
- private var navState = NavState(canSkipConfirm)
+ /** A flow that indicates the [FingerprintFlow] */
+ val fingerprintFlow: Flow<FingerprintFlow?> = _fingerprintFlow.asStateFlow()
private val _navigationStep =
MutableStateFlow(
- InternalNavigationStep(
- PlaceHolderState,
- Start.next(navState),
- forward = false,
- canNavigate = true
- )
+ InternalNavigationStep(PlaceHolderState, firstStep, forward = false, canNavigate = true)
)
init {
@@ -96,6 +80,10 @@
*/
val navigationViewModel: Flow<NavigationStep> = _navigationStep.asStateFlow()
+ /** This action indicates that the UI should actually update the navigation to the given step. */
+ val navigationAction: Flow<NavigationStep?> =
+ _navigationStep.shareIn(viewModelScope, SharingStarted.Lazily, 0)
+
/** Used to start the next step of Fingerprint Enrollment. */
fun nextStep() {
viewModelScope.launch {
@@ -130,6 +118,7 @@
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
private val fingerprintGatekeeperViewModel: FingerprintGatekeeperViewModel,
private val canSkipConfirm: Boolean,
+ private val fingerprintFlow: FingerprintFlow,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
@@ -137,11 +126,14 @@
modelClass: Class<T>,
): T {
+ val navState = NavState(canSkipConfirm)
return FingerprintEnrollNavigationViewModel(
backgroundDispatcher,
fingerprintManagerInteractor,
fingerprintGatekeeperViewModel,
- canSkipConfirm,
+ Start.next(navState),
+ navState,
+ fingerprintFlow,
)
as T
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/NextStepViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/NextStepViewModel.kt
index e99b8f9..b68f6d6 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/NextStepViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/NextStepViewModel.kt
@@ -57,7 +57,7 @@
* This state is the initial state for the current step, and will be used to determine if the user
* needs to [LaunchConfirmDeviceCredential] if not, it will go to [Intro]
*/
-object Start : NextStepViewModel() {
+data object Start : NextStepViewModel() {
override fun next(state: NavState): NextStepViewModel =
if (state.confirmedDevice) Intro else LaunchConfirmDeviceCredential
@@ -71,19 +71,19 @@
}
/** State for the FingerprintEnrollment introduction */
-object Intro : NextStepViewModel() {
+data object Intro : NextStepViewModel() {
override fun next(state: NavState): NextStepViewModel = Education
override fun prev(state: NavState): NextStepViewModel = Finish(null)
}
/** State for the FingerprintEnrollment education */
-object Education : NextStepViewModel() {
+data object Education : NextStepViewModel() {
override fun next(state: NavState): NextStepViewModel = Enrollment
override fun prev(state: NavState): NextStepViewModel = Intro
}
/** State for the FingerprintEnrollment enrollment */
-object Enrollment : NextStepViewModel() {
+data object Enrollment : NextStepViewModel() {
override fun next(state: NavState): NextStepViewModel = Confirmation
override fun prev(state: NavState): NextStepViewModel = Education
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/binder/FingerprintSettingsViewBinder.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/binder/FingerprintSettingsViewBinder.kt
index e66b4cd..debdfb8 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/binder/FingerprintSettingsViewBinder.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/binder/FingerprintSettingsViewBinder.kt
@@ -19,8 +19,8 @@
import android.hardware.fingerprint.FingerprintManager
import android.util.Log
import androidx.lifecycle.LifecycleCoroutineScope
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder.FingerprintView
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.EnrollAdditionalFingerprint
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.EnrollFirstFingerprint
@@ -66,21 +66,21 @@
/** Indicates what result should be set for the returning callee */
fun setResultExternal(resultCode: Int)
/** Indicates the settings UI should be shown */
- fun showSettings(enrolledFingerprints: List<FingerprintViewModel>)
+ fun showSettings(enrolledFingerprints: List<FingerprintData>)
/** Updates the add fingerprints preference */
fun updateAddFingerprintsPreference(canEnroll: Boolean, maxFingerprints: Int)
/** Updates the sfps fingerprints preference */
fun updateSfpsPreference(isSfpsPrefVisible: Boolean)
/** Indicates that a user has been locked out */
- fun userLockout(authAttemptViewModel: FingerprintAuthAttemptViewModel.Error)
+ fun userLockout(authAttemptViewModel: FingerprintAuthAttemptModel.Error)
/** Indicates a fingerprint preference should be highlighted */
suspend fun highlightPref(fingerId: Int)
/** Indicates a user should be prompted to delete a fingerprint */
- suspend fun askUserToDeleteDialog(fingerprintViewModel: FingerprintViewModel): Boolean
+ suspend fun askUserToDeleteDialog(fingerprintViewModel: FingerprintData): Boolean
/** Indicates a user should be asked to renae ma dialog */
suspend fun askUserToRenameDialog(
- fingerprintViewModel: FingerprintViewModel
- ): Pair<FingerprintViewModel, String>?
+ fingerprintViewModel: FingerprintData
+ ): Pair<FingerprintData, String>?
}
fun bind(
@@ -131,10 +131,10 @@
lifecycleScope.launch {
viewModel.authFlow.filterNotNull().collect {
when (it) {
- is FingerprintAuthAttemptViewModel.Success -> {
+ is FingerprintAuthAttemptModel.Success -> {
view.highlightPref(it.fingerId)
}
- is FingerprintAuthAttemptViewModel.Error -> {
+ is FingerprintAuthAttemptModel.Error -> {
if (it.error == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) {
view.userLockout(it)
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintDeletionDialog.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintDeletionDialog.kt
index 32b50c5..71a22eb 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintDeletionDialog.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintDeletionDialog.kt
@@ -26,7 +26,7 @@
import android.os.UserManager
import androidx.appcompat.app.AlertDialog
import com.android.settings.R
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
import com.android.settings.core.instrumentation.InstrumentedDialogFragment
import kotlin.coroutines.resume
import kotlinx.coroutines.suspendCancellableCoroutine
@@ -34,7 +34,7 @@
private const val KEY_IS_LAST_FINGERPRINT = "IS_LAST_FINGERPRINT"
class FingerprintDeletionDialog : InstrumentedDialogFragment() {
- private lateinit var fingerprintViewModel: FingerprintViewModel
+ private lateinit var fingerprintViewModel: FingerprintData
private var isLastFingerprint: Boolean = false
private lateinit var alertDialog: AlertDialog
lateinit var onClickListener: DialogInterface.OnClickListener
@@ -51,7 +51,7 @@
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val fp = requireArguments().get(KEY_FINGERPRINT) as android.hardware.fingerprint.Fingerprint
- fingerprintViewModel = FingerprintViewModel(fp.name.toString(), fp.biometricId, fp.deviceId)
+ fingerprintViewModel = FingerprintData(fp.name.toString(), fp.biometricId, fp.deviceId)
isLastFingerprint = requireArguments().getBoolean(KEY_IS_LAST_FINGERPRINT)
val title = getString(R.string.fingerprint_delete_title, fingerprintViewModel.name)
var message = getString(R.string.fingerprint_v2_delete_message, fingerprintViewModel.name)
@@ -95,9 +95,9 @@
companion object {
private const val KEY_FINGERPRINT = "fingerprint"
suspend fun showInstance(
- fp: FingerprintViewModel,
- lastFingerprint: Boolean,
- target: FingerprintSettingsV2Fragment,
+ fp: FingerprintData,
+ lastFingerprint: Boolean,
+ target: FingerprintSettingsV2Fragment,
) = suspendCancellableCoroutine { continuation ->
val dialog = FingerprintDeletionDialog()
dialog.onClickListener = DialogInterface.OnClickListener { _, _ -> continuation.resume(true) }
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsPreference.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsPreference.kt
index b1e5097..ea26946 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsPreference.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsPreference.kt
@@ -22,7 +22,7 @@
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceViewHolder
import com.android.settings.R
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
import com.android.settingslib.widget.TwoTargetPreference
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -30,10 +30,10 @@
private const val TAG = "FingerprintSettingsPreference"
class FingerprintSettingsPreference(
- context: Context,
- val fingerprintViewModel: FingerprintViewModel,
- val fragment: FingerprintSettingsV2Fragment,
- val isLastFingerprint: Boolean
+ context: Context,
+ val fingerprintViewModel: FingerprintData,
+ val fragment: FingerprintSettingsV2Fragment,
+ val isLastFingerprint: Boolean
) : TwoTargetPreference(context) {
private lateinit var myView: View
@@ -79,7 +79,7 @@
return FingerprintDeletionDialog.showInstance(fingerprintViewModel, isLastFingerprint, fragment)
}
- suspend fun askUserToRenameDialog(): Pair<FingerprintViewModel, String>? {
+ suspend fun askUserToRenameDialog(): Pair<FingerprintData, String>? {
return FingerprintSettingsRenameDialog.showInstance(fingerprintViewModel, fragment)
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsRenameDialog.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsRenameDialog.kt
index 9bde0b0..ff469f1 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsRenameDialog.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsRenameDialog.kt
@@ -27,7 +27,7 @@
import android.widget.ImeAwareEditText
import androidx.appcompat.app.AlertDialog
import com.android.settings.R
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
import com.android.settings.core.instrumentation.InstrumentedDialogFragment
import kotlin.coroutines.resume
import kotlinx.coroutines.suspendCancellableCoroutine
@@ -46,7 +46,7 @@
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
Log.d(TAG, "onCreateDialog $this")
val fp = requireArguments().get(KEY_FINGERPRINT) as android.hardware.fingerprint.Fingerprint
- val fingerprintViewModel = FingerprintViewModel(fp.name.toString(), fp.biometricId, fp.deviceId)
+ val fingerprintViewModel = FingerprintData(fp.name.toString(), fp.biometricId, fp.deviceId)
val context = requireContext()
val alertDialog =
@@ -101,7 +101,7 @@
companion object {
private const val KEY_FINGERPRINT = "fingerprint"
- suspend fun showInstance(fp: FingerprintViewModel, target: FingerprintSettingsV2Fragment) =
+ suspend fun showInstance(fp: FingerprintData, target: FingerprintSettingsV2Fragment) =
suspendCancellableCoroutine { continuation ->
val dialog = FingerprintSettingsRenameDialog()
val onClick =
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt
index c818566..c22a5a7 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt
@@ -46,8 +46,10 @@
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.repository.PressToAuthProviderImpl
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
+import com.android.settings.biometrics.fingerprint2.shared.model.Settings
import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsViewModel
@@ -142,7 +144,7 @@
}
}
- override fun userLockout(authAttemptViewModel: FingerprintAuthAttemptViewModel.Error) {
+ override fun userLockout(authAttemptViewModel: FingerprintAuthAttemptModel.Error) {
Toast.makeText(activity, authAttemptViewModel.message, Toast.LENGTH_SHORT).show()
}
@@ -186,40 +188,46 @@
val backgroundDispatcher = Dispatchers.IO
val activity = requireActivity()
val userHandle = activity.user.identifier
+ // Note that SUW should not be launching FingerprintSettings
+ val isAnySuw = Settings
+
+ val pressToAuthProvider = {
+ var toReturn: Int =
+ Secure.getIntForUser(
+ context.contentResolver,
+ Secure.SFPS_PERFORMANT_AUTH_ENABLED,
+ -1,
+ userHandle,
+ )
+ if (toReturn == -1) {
+ toReturn =
+ if (
+ context.resources.getBoolean(com.android.internal.R.bool.config_performantAuthDefault)
+ ) {
+ 1
+ } else {
+ 0
+ }
+ Secure.putIntForUser(
+ context.contentResolver,
+ Secure.SFPS_PERFORMANT_AUTH_ENABLED,
+ toReturn,
+ userHandle
+ )
+ }
+
+ toReturn == 1
+ }
val interactor =
FingerprintManagerInteractorImpl(
context.applicationContext,
backgroundDispatcher,
fingerprintManager,
- GatekeeperPasswordProvider(LockPatternUtils(context.applicationContext))
- ) {
- var toReturn: Int =
- Secure.getIntForUser(
- context.contentResolver,
- Secure.SFPS_PERFORMANT_AUTH_ENABLED,
- -1,
- userHandle,
- )
- if (toReturn == -1) {
- toReturn =
- if (
- context.resources.getBoolean(com.android.internal.R.bool.config_performantAuthDefault)
- ) {
- 1
- } else {
- 0
- }
- Secure.putIntForUser(
- context.contentResolver,
- Secure.SFPS_PERFORMANT_AUTH_ENABLED,
- toReturn,
- userHandle
- )
- }
-
- toReturn == 1
- }
+ GatekeeperPasswordProvider(LockPatternUtils(context.applicationContext)),
+ PressToAuthProviderImpl(context),
+ isAnySuw
+ )
val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
val challenge = intent.getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, -1L)
@@ -292,18 +300,18 @@
}
/** Used to indicate that preference has been clicked */
- fun onPrefClicked(fingerprintViewModel: FingerprintViewModel) {
+ fun onPrefClicked(fingerprintViewModel: FingerprintData) {
Log.d(TAG, "onPrefClicked(${fingerprintViewModel})")
settingsViewModel.onPrefClicked(fingerprintViewModel)
}
/** Used to indicate that a delete pref has been clicked */
- fun onDeletePrefClicked(fingerprintViewModel: FingerprintViewModel) {
+ fun onDeletePrefClicked(fingerprintViewModel: FingerprintData) {
Log.d(TAG, "onDeletePrefClicked(${fingerprintViewModel})")
settingsViewModel.onDeleteClicked(fingerprintViewModel)
}
- override fun showSettings(enrolledFingerprints: List<FingerprintViewModel>) {
+ override fun showSettings(enrolledFingerprints: List<FingerprintData>) {
val category =
this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINTS_ENROLLED_CATEGORY)
as PreferenceCategory?
@@ -422,7 +430,7 @@
}
}
- override suspend fun askUserToDeleteDialog(fingerprintViewModel: FingerprintViewModel): Boolean {
+ override suspend fun askUserToDeleteDialog(fingerprintViewModel: FingerprintData): Boolean {
Log.d(TAG, "showing delete dialog for (${fingerprintViewModel})")
try {
@@ -446,8 +454,8 @@
}
override suspend fun askUserToRenameDialog(
- fingerprintViewModel: FingerprintViewModel
- ): Pair<FingerprintViewModel, String>? {
+ fingerprintViewModel: FingerprintData
+ ): Pair<FingerprintData, String>? {
Log.d(TAG, "showing rename dialog for (${fingerprintViewModel})")
try {
val toReturn =
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsViewModel.kt
index fa1e5e1..164f79f 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsViewModel.kt
@@ -22,8 +22,8 @@
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
@@ -53,11 +53,11 @@
private val backgroundDispatcher: CoroutineDispatcher,
private val navigationViewModel: FingerprintSettingsNavigationViewModel,
) : ViewModel() {
- private val _enrolledFingerprints: MutableStateFlow<List<FingerprintViewModel>?> =
+ private val _enrolledFingerprints: MutableStateFlow<List<FingerprintData>?> =
MutableStateFlow(null)
/** Represents the stream of enrolled fingerprints. */
- val enrolledFingerprints: Flow<List<FingerprintViewModel>> =
+ val enrolledFingerprints: Flow<List<FingerprintData>> =
_enrolledFingerprints.asStateFlow().filterNotNull().filterOnlyWhenSettingsIsShown()
/** Represents the stream of the information of "Add Fingerprint" preference. */
@@ -95,10 +95,10 @@
private val _sensorNullOrEmpty: Flow<Boolean> =
fingerprintManagerInteractor.sensorPropertiesInternal.map { it == null }
- private val _isLockedOut: MutableStateFlow<FingerprintAuthAttemptViewModel.Error?> =
+ private val _isLockedOut: MutableStateFlow<FingerprintAuthAttemptModel.Error?> =
MutableStateFlow(null)
- private val _authSucceeded: MutableSharedFlow<FingerprintAuthAttemptViewModel.Success?> =
+ private val _authSucceeded: MutableSharedFlow<FingerprintAuthAttemptModel.Success?> =
MutableSharedFlow()
private val _attemptsSoFar: MutableStateFlow<Int> = MutableStateFlow(0)
@@ -164,7 +164,7 @@
.distinctUntilChanged()
/** Represents a consistent stream of authentication attempts. */
- val authFlow: Flow<FingerprintAuthAttemptViewModel> =
+ val authFlow: Flow<FingerprintAuthAttemptModel> =
canAuthenticate
.transformLatest {
try {
@@ -173,11 +173,11 @@
Log.d(TAG, "canAuthenticate authing")
attemptingAuth()
when (val authAttempt = fingerprintManagerInteractor.authenticate()) {
- is FingerprintAuthAttemptViewModel.Success -> {
+ is FingerprintAuthAttemptModel.Success -> {
onAuthSuccess(authAttempt)
emit(authAttempt)
}
- is FingerprintAuthAttemptViewModel.Error -> {
+ is FingerprintAuthAttemptModel.Error -> {
if (authAttempt.error == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) {
lockout(authAttempt)
emit(authAttempt)
@@ -219,7 +219,7 @@
}
/** The fingerprint delete button has been clicked. */
- fun onDeleteClicked(fingerprintViewModel: FingerprintViewModel) {
+ fun onDeleteClicked(fingerprintViewModel: FingerprintData) {
viewModelScope.launch {
if (_isShowingDialog.value == null || navigationViewModel.nextStep.value != ShowSettings) {
_isShowingDialog.tryEmit(PreferenceViewModel.DeleteDialog(fingerprintViewModel))
@@ -230,7 +230,7 @@
}
/** The rename fingerprint dialog has been clicked. */
- fun onPrefClicked(fingerprintViewModel: FingerprintViewModel) {
+ fun onPrefClicked(fingerprintViewModel: FingerprintData) {
viewModelScope.launch {
if (_isShowingDialog.value == null || navigationViewModel.nextStep.value != ShowSettings) {
_isShowingDialog.tryEmit(PreferenceViewModel.RenameDialog(fingerprintViewModel))
@@ -241,7 +241,7 @@
}
/** A request to delete a fingerprint */
- fun deleteFingerprint(fp: FingerprintViewModel) {
+ fun deleteFingerprint(fp: FingerprintData) {
viewModelScope.launch(backgroundDispatcher) {
if (fingerprintManagerInteractor.removeFingerprint(fp)) {
updateEnrolledFingerprints()
@@ -250,7 +250,7 @@
}
/** A request to rename a fingerprint */
- fun renameFingerprint(fp: FingerprintViewModel, newName: String) {
+ fun renameFingerprint(fp: FingerprintData, newName: String) {
viewModelScope.launch {
fingerprintManagerInteractor.renameFingerprint(fp, newName)
updateEnrolledFingerprints()
@@ -261,12 +261,12 @@
_attemptsSoFar.update { it + 1 }
}
- private suspend fun onAuthSuccess(success: FingerprintAuthAttemptViewModel.Success) {
+ private suspend fun onAuthSuccess(success: FingerprintAuthAttemptModel.Success) {
_authSucceeded.emit(success)
_attemptsSoFar.update { 0 }
}
- private fun lockout(attemptViewModel: FingerprintAuthAttemptViewModel.Error) {
+ private fun lockout(attemptViewModel: FingerprintAuthAttemptModel.Error) {
_isLockedOut.update { attemptViewModel }
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/PreferenceViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/PreferenceViewModel.kt
index 4c33f7f..181da4e 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/PreferenceViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/PreferenceViewModel.kt
@@ -16,15 +16,15 @@
package com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
/** Classed use to represent a Dialogs state. */
sealed class PreferenceViewModel {
data class RenameDialog(
- val fingerprintViewModel: FingerprintViewModel,
+ val fingerprintViewModel: FingerprintData,
) : PreferenceViewModel()
data class DeleteDialog(
- val fingerprintViewModel: FingerprintViewModel,
+ val fingerprintViewModel: FingerprintData,
) : PreferenceViewModel()
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java
new file mode 100644
index 0000000..387ab7e
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.audiosharing;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ImageButton;
+
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.R;
+import com.android.settings.widget.ValidatedEditTextPreference;
+
+public class AudioSharingNamePreference extends ValidatedEditTextPreference {
+ private static final String TAG = "AudioSharingNamePreference";
+
+ public AudioSharingNamePreference(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ initialize();
+ }
+
+ public AudioSharingNamePreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initialize();
+ }
+
+ public AudioSharingNamePreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initialize();
+ }
+
+ public AudioSharingNamePreference(Context context) {
+ super(context);
+ initialize();
+ }
+
+ private void initialize() {
+ setLayoutResource(
+ com.android.settingslib.widget.preference.twotarget.R.layout.preference_two_target);
+ setWidgetLayoutResource(R.layout.preference_widget_qrcode);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+ final ImageButton shareButton = (ImageButton) holder.findViewById(R.id.button_icon);
+ shareButton.setImageDrawable(getContext().getDrawable(R.drawable.ic_qrcode_24dp));
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
new file mode 100644
index 0000000..18c9bfd
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.audiosharing;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.flags.Flags;
+import com.android.settings.widget.ValidatedEditTextPreference;
+
+public class AudioSharingNamePreferenceController extends BasePreferenceController
+ implements ValidatedEditTextPreference.Validator,
+ Preference.OnPreferenceChangeListener,
+ DefaultLifecycleObserver {
+
+ private static final String TAG = "AudioSharingNamePreferenceController";
+
+ private static final String PREF_KEY = "audio_sharing_stream_name";
+
+ protected Preference mPreference;
+
+ private AudioSharingNameTextValidator mAudioSharingNameTextValidator;
+
+ public AudioSharingNamePreferenceController(Context context) {
+ super(context, PREF_KEY);
+ mAudioSharingNameTextValidator = new AudioSharingNameTextValidator();
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return Flags.enableLeAudioSharing() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return PREF_KEY;
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ // TODO: update broadcast when name is changed.
+ return true;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mPreference = screen.findPreference(getPreferenceKey());
+ }
+
+ @Override
+ public boolean isTextValid(String value) {
+ return mAudioSharingNameTextValidator.isTextValid(value);
+ }
+
+ @Override
+ public void onStart(@NonNull LifecycleOwner owner) {
+ // TODO
+ }
+
+ @Override
+ public void onStop(@NonNull LifecycleOwner owner) {
+ // TODO
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintViewModel.kt b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNameTextValidator.java
similarity index 60%
copy from src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintViewModel.kt
copy to src/com/android/settings/connecteddevice/audiosharing/AudioSharingNameTextValidator.java
index db28e79..9492961 100644
--- a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintViewModel.kt
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNameTextValidator.java
@@ -14,21 +14,14 @@
* limitations under the License.
*/
-package com.android.settings.biometrics.fingerprint2.shared.model
+package com.android.settings.connecteddevice.audiosharing;
-data class FingerprintViewModel(
- val name: String,
- val fingerId: Int,
- val deviceId: Long,
-)
+import com.android.settings.widget.ValidatedEditTextPreference;
-sealed class FingerprintAuthAttemptViewModel {
- data class Success(
- val fingerId: Int,
- ) : FingerprintAuthAttemptViewModel()
-
- data class Error(
- val error: Int,
- val message: String,
- ) : FingerprintAuthAttemptViewModel()
+public class AudioSharingNameTextValidator implements ValidatedEditTextPreference.Validator {
+ @Override
+ public boolean isTextValid(String value) {
+ // TODO: Add validate rule if applicable.
+ return true;
+ }
}
diff --git a/src/com/android/settings/datausage/CellDataPreference.java b/src/com/android/settings/datausage/CellDataPreference.java
index 9374217..3bd3ecc 100644
--- a/src/com/android/settings/datausage/CellDataPreference.java
+++ b/src/com/android/settings/datausage/CellDataPreference.java
@@ -26,11 +26,10 @@
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
-import android.widget.Checkable;
+import android.widget.CompoundButton;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog.Builder;
-import androidx.core.content.res.TypedArrayUtils;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
@@ -51,12 +50,10 @@
public int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
public boolean mChecked;
public boolean mMultiSimDialog;
- private MobileDataEnabledListener mDataStateListener;
+ private final MobileDataEnabledListener mDataStateListener;
public CellDataPreference(Context context, AttributeSet attrs) {
- super(context, attrs, TypedArrayUtils.getAttr(context,
- androidx.preference.R.attr.switchPreferenceStyle,
- android.R.attr.switchPreferenceStyle));
+ super(context, attrs, androidx.preference.R.attr.switchPreferenceCompatStyle);
mDataStateListener = new MobileDataEnabledListener(context, this);
}
@@ -170,9 +167,10 @@
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
- final View switchView = holder.findViewById(android.R.id.switch_widget);
+ final CompoundButton switchView =
+ (CompoundButton) holder.findViewById(androidx.preference.R.id.switchWidget);
switchView.setClickable(false);
- ((Checkable) switchView).setChecked(mChecked);
+ switchView.setChecked(mChecked);
}
@Override
diff --git a/src/com/android/settings/datausage/ChartDataUsagePreference.java b/src/com/android/settings/datausage/ChartDataUsagePreference.java
index e5a7307..e8e2109 100644
--- a/src/com/android/settings/datausage/ChartDataUsagePreference.java
+++ b/src/com/android/settings/datausage/ChartDataUsagePreference.java
@@ -112,9 +112,6 @@
// increment by current bucket total
totalData += data.getUsage();
- if (points.size() == 1) {
- points.put(toInt(startTime - mStart) - 1, -1);
- }
points.put(toInt(startTime - mStart + 1), (int) (totalData / RESOLUTION));
points.put(toInt(endTime - mStart), (int) (totalData / RESOLUTION));
}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java
index 9d7b629..a7deaf9 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java
@@ -45,6 +45,9 @@
// Caches app label and icon to improve loading performance.
static final Map<String, BatteryEntry.NameAndIcon> sResourceCache = new HashMap<>();
+ // Caches package name and uid to improve loading performance.
+ static final Map<String, Integer> sPackageNameAndUidCache = new HashMap<>();
+
// Whether a specific item is valid to launch restriction page?
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
static final Map<String, Boolean> sValidForRestriction = new HashMap<>();
@@ -289,10 +292,20 @@
return false;
}
- final int uid = BatteryUtils.getInstance(mContext).getPackageUid(packageName);
+ final int uid = getPackageUid(packageName);
return uid == BatteryUtils.UID_REMOVED_APPS || uid == BatteryUtils.UID_NULL;
}
+ private int getPackageUid(String packageName) {
+ if (sPackageNameAndUidCache.containsKey(packageName)) {
+ return sPackageNameAndUidCache.get(packageName);
+ }
+
+ int uid = BatteryUtils.getInstance(mContext).getPackageUid(packageName);
+ sPackageNameAndUidCache.put(packageName, uid);
+ return uid;
+ }
+
void loadLabelAndIcon() {
if (mIsLoaded) {
return;
@@ -498,10 +511,11 @@
return builder.toString();
}
- /** Clears app icon and label cache data. */
+ /** Clears all cache data. */
public static void clearCache() {
sResourceCache.clear();
sValidForRestriction.clear();
+ sPackageNameAndUidCache.clear();
}
private Drawable getBadgeIconForUser(Drawable icon) {
diff --git a/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManager.java b/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManager.java
new file mode 100644
index 0000000..be72e56
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManager.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.datasaver;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.net.NetworkPolicyManager;
+
+import androidx.annotation.VisibleForTesting;
+
+/** A class to dynamically manage per apps {@link NetworkPolicyManager} POLICY_ flags. */
+public final class DynamicDenylistManager {
+
+ private static final String TAG = "DynamicDenylistManager";
+ private static final String PREF_KEY_MANUAL_DENY = "manual_denylist_preference";
+ private static final String PREF_KEY_DYNAMIC_DENY = "dynamic_denylist_preference";
+
+ private final Context mContext;
+ private final NetworkPolicyManager mNetworkPolicyManager;
+
+ private static DynamicDenylistManager sInstance;
+
+ /** @return a DynamicDenylistManager object */
+ public static DynamicDenylistManager getInstance(Context context) {
+ synchronized (DynamicDenylistManager.class) {
+ if (sInstance == null) {
+ sInstance = new DynamicDenylistManager(context);
+ }
+ return sInstance;
+ }
+ }
+
+ DynamicDenylistManager(Context context) {
+ mContext = context.getApplicationContext();
+ mNetworkPolicyManager = NetworkPolicyManager.from(mContext);
+ }
+
+ /** Update the target uid policy in {@link #getManualDenylistPref()}. */
+ public void updateManualDenylist(String uid, int policy) {
+ if (policy != NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND) {
+ getManualDenylistPref().edit().remove(uid).apply();
+ } else {
+ getManualDenylistPref().edit().putInt(uid, policy).apply();
+ }
+ }
+
+ /** Return true if the target uid is in {@link #getManualDenylistPref()}. */
+ public boolean isInManualDenylist(String uid) {
+ return getManualDenylistPref().contains(uid);
+ }
+
+ /** Clear all data in {@link #getManualDenylistPref()} */
+ public void clearManualDenylistPref() {
+ getManualDenylistPref().edit().clear().apply();
+ }
+
+ /** Clear all data in {@link #getDynamicDenylistPref()} */
+ public void clearDynamicDenylistPref() {
+ getDynamicDenylistPref().edit().clear().apply();
+ }
+
+ @VisibleForTesting
+ SharedPreferences getManualDenylistPref() {
+ return mContext.getSharedPreferences(PREF_KEY_MANUAL_DENY, Context.MODE_PRIVATE);
+ }
+
+ @VisibleForTesting
+ SharedPreferences getDynamicDenylistPref() {
+ return mContext.getSharedPreferences(PREF_KEY_DYNAMIC_DENY, Context.MODE_PRIVATE);
+ }
+}
diff --git a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
index bfe0749..43fc9cf 100644
--- a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
+++ b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
@@ -16,6 +16,7 @@
package com.android.settings.localepicker;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.graphics.Canvas;
import android.os.Bundle;
@@ -38,6 +39,7 @@
import com.android.internal.app.LocalePicker;
import com.android.internal.app.LocaleStore;
import com.android.settings.R;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settings.shortcut.ShortcutsUpdateTask;
import java.text.NumberFormat;
@@ -225,6 +227,12 @@
"Negative position in onItemMove %d -> %d", fromPosition, toPosition));
}
+ if (fromPosition != toPosition) {
+ FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
+ .action(mContext, SettingsEnums.ACTION_REORDER_LANGUAGE,
+ mDragLocale.getLocale().toLanguageTag() + " move to " + toPosition);
+ }
+
notifyItemChanged(fromPosition); // to update the numbers
notifyItemChanged(toPosition);
notifyItemMoved(fromPosition, toPosition);
@@ -263,6 +271,9 @@
for (int i = itemCount - 1; i >= 0; i--) {
localeInfo = mFeedItemList.get(i);
if (localeInfo.getChecked()) {
+ FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
+ .action(mContext, SettingsEnums.ACTION_REMOVE_LANGUAGE,
+ localeInfo.getLocale().toLanguageTag());
mFeedItemList.remove(i);
}
}
diff --git a/src/com/android/settings/localepicker/LocaleHelperPreferenceController.java b/src/com/android/settings/localepicker/LocaleHelperPreferenceController.java
index a639c9d..b962b9e 100644
--- a/src/com/android/settings/localepicker/LocaleHelperPreferenceController.java
+++ b/src/com/android/settings/localepicker/LocaleHelperPreferenceController.java
@@ -16,6 +16,7 @@
package com.android.settings.localepicker;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
@@ -24,8 +25,10 @@
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.widget.FooterPreference;
/**
@@ -36,8 +39,11 @@
private static final String KEY_FOOTER_LANGUAGE_PICKER = "footer_languages_picker";
+ private final MetricsFeatureProvider mMetricsFeatureProvider;
+
public LocaleHelperPreferenceController(Context context) {
super(context);
+ mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
}
@Override
@@ -72,6 +78,7 @@
mContext.getString(R.string.link_locale_picker_footer_learn_more),
mContext.getClass().getName());
if (intent != null) {
+ mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_LANGUAGES_LEARN_MORE);
mContext.startActivity(intent);
} else {
Log.w(TAG, "HelpIntent is null");
diff --git a/src/com/android/settings/localepicker/LocaleListEditor.java b/src/com/android/settings/localepicker/LocaleListEditor.java
index bdda549..28f066a 100644
--- a/src/com/android/settings/localepicker/LocaleListEditor.java
+++ b/src/com/android/settings/localepicker/LocaleListEditor.java
@@ -224,6 +224,8 @@
localeInfo = mayAppendUnicodeTags(localeInfo, preferencesTags);
mAdapter.addLocale(localeInfo);
updateVisibilityOfRemoveMenu();
+ mMetricsFeatureProvider.action(getContext(), SettingsEnums.ACTION_ADD_LANGUAGE,
+ localeInfo.getLocale().toLanguageTag());
} else if (requestCode == DIALOG_CONFIRM_SYSTEM_DEFAULT) {
localeInfo = mAdapter.getFeedItemList().get(0);
if (resultCode == Activity.RESULT_OK) {
diff --git a/src/com/android/settings/network/MobileNetworkListFragment.java b/src/com/android/settings/network/MobileNetworkListFragment.java
deleted file mode 100644
index 3de05af..0000000
--- a/src/com/android/settings/network/MobileNetworkListFragment.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.network;
-
-import android.app.settings.SettingsEnums;
-import android.content.Context;
-import android.os.UserManager;
-
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.settings.R;
-import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.network.telephony.MobileNetworkUtils;
-import com.android.settings.search.BaseSearchIndexProvider;
-import com.android.settingslib.search.SearchIndexable;
-
-@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
-public class MobileNetworkListFragment extends DashboardFragment {
- private static final String LOG_TAG = "NetworkListFragment";
-
- private static final String KEY_ADD_SIM = "add_sim";
-
- @Override
- public void onResume() {
- super.onResume();
- // Disable the animation of the preference list
- final RecyclerView prefListView = getListView();
- if (prefListView != null) {
- prefListView.setItemAnimator(null);
- }
-
- findPreference(KEY_ADD_SIM).setVisible(MobileNetworkUtils.showEuiccSettings(getContext()));
- }
-
- @Override
- protected int getPreferenceScreenResId() {
- return R.xml.network_provider_sims_list;
- }
-
- @Override
- protected String getLogTag() {
- return LOG_TAG;
- }
-
- @Override
- public int getMetricsCategory() {
- return SettingsEnums.MOBILE_NETWORK_LIST;
- }
-
- public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
- new BaseSearchIndexProvider(R.xml.network_provider_sims_list) {
-
- @Override
- protected boolean isPageSearchEnabled(Context context) {
- return SubscriptionUtil.isSimHardwareVisible(context) &&
- context.getSystemService(UserManager.class).isAdminUser();
- }
- };
-}
diff --git a/src/com/android/settings/network/MobileNetworkListFragment.kt b/src/com/android/settings/network/MobileNetworkListFragment.kt
new file mode 100644
index 0000000..5000afd
--- /dev/null
+++ b/src/com/android/settings/network/MobileNetworkListFragment.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network
+
+import android.app.settings.SettingsEnums
+import android.content.Context
+import android.os.Bundle
+import android.provider.Settings
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import androidx.preference.Preference
+import com.android.settings.R
+import com.android.settings.SettingsPreferenceFragment
+import com.android.settings.dashboard.DashboardFragment
+import com.android.settings.network.telephony.MobileNetworkUtils
+import com.android.settings.search.BaseSearchIndexProvider
+import com.android.settings.utils.observeSettingsGlobalBoolean
+import com.android.settingslib.search.SearchIndexable
+import com.android.settingslib.spaprivileged.framework.common.userManager
+
+@SearchIndexable(forTarget = SearchIndexable.ALL and SearchIndexable.ARC.inv())
+class MobileNetworkListFragment : DashboardFragment() {
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ observeAirplaneModeAndFinishIfOn()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ // Disable the animation of the preference list
+ listView.itemAnimator = null
+
+ findPreference<Preference>(KEY_ADD_SIM)!!.isVisible =
+ MobileNetworkUtils.showEuiccSettings(context)
+ }
+
+ override fun getPreferenceScreenResId() = R.xml.network_provider_sims_list
+
+ override fun getLogTag() = LOG_TAG
+
+ override fun getMetricsCategory() = SettingsEnums.MOBILE_NETWORK_LIST
+
+ companion object {
+ private const val LOG_TAG = "NetworkListFragment"
+ private const val KEY_ADD_SIM = "add_sim"
+
+ @JvmStatic
+ fun SettingsPreferenceFragment.observeAirplaneModeAndFinishIfOn() {
+ requireContext().observeSettingsGlobalBoolean(
+ name = Settings.Global.AIRPLANE_MODE_ON,
+ lifecycle = viewLifecycleOwner.lifecycle,
+ ) { isAirplaneModeOn: Boolean ->
+ if (isAirplaneModeOn) {
+ finish()
+ }
+ }
+ }
+
+ @JvmField
+ val SEARCH_INDEX_DATA_PROVIDER = SearchIndexProvider()
+
+ @VisibleForTesting
+ class SearchIndexProvider : BaseSearchIndexProvider(R.xml.network_provider_sims_list) {
+ public override fun isPageSearchEnabled(context: Context): Boolean =
+ SubscriptionUtil.isSimHardwareVisible(context) &&
+ context.userManager.isAdminUser
+ }
+ }
+}
diff --git a/src/com/android/settings/network/apn/ApnEditPageProvider.kt b/src/com/android/settings/network/apn/ApnEditPageProvider.kt
index 0e23a0e..81da0bf 100644
--- a/src/com/android/settings/network/apn/ApnEditPageProvider.kt
+++ b/src/com/android/settings/network/apn/ApnEditPageProvider.kt
@@ -96,7 +96,7 @@
getNetworkTypeSelectedOptionsState(apnData.networkType)
}
RegularScaffold(
- title = stringResource(id = R.string.apn_edit),
+ title = if(apnDataInit.newApn) stringResource(id = R.string.apn_add) else stringResource(id = R.string.apn_edit),
actions = {
IconButton(onClick = {
validateAndSaveApnData(
diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
index afc1b7e..7e290b8 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java
+++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
@@ -16,6 +16,8 @@
package com.android.settings.network.telephony;
+import static com.android.settings.network.MobileNetworkListFragment.observeAirplaneModeAndFinishIfOn;
+
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.Context;
@@ -31,7 +33,10 @@
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import android.view.View;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
@@ -327,6 +332,12 @@
}
@Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ observeAirplaneModeAndFinishIfOn(this);
+ }
+
+ @Override
public void onResume() {
super.onResume();
mMobileNetworkRepository.addRegister(this, this, mSubId);
@@ -361,11 +372,6 @@
super.onPause();
}
- @Override
- public void onDestroy() {
- super.onDestroy();
- }
-
@VisibleForTesting
void onRestoreInstance(Bundle icicle) {
if (icicle != null) {
diff --git a/src/com/android/settings/regionalpreferences/FirstDayOfWeekItemListController.java b/src/com/android/settings/regionalpreferences/FirstDayOfWeekItemListController.java
index 03a59de..d509d2e 100644
--- a/src/com/android/settings/regionalpreferences/FirstDayOfWeekItemListController.java
+++ b/src/com/android/settings/regionalpreferences/FirstDayOfWeekItemListController.java
@@ -16,6 +16,7 @@
package com.android.settings.regionalpreferences;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import com.android.settings.R;
@@ -57,4 +58,9 @@
protected String[] getUnitValues() {
return mContext.getResources().getStringArray(R.array.first_day_of_week);
}
+
+ @Override
+ protected int getMetricsActionKey() {
+ return SettingsEnums.ACTION_SET_FIRST_DAY_OF_WEEK;
+ }
}
diff --git a/src/com/android/settings/regionalpreferences/RegionalPreferenceListBasePreferenceController.java b/src/com/android/settings/regionalpreferences/RegionalPreferenceListBasePreferenceController.java
index 2f2bf76..ac0e7ee 100644
--- a/src/com/android/settings/regionalpreferences/RegionalPreferenceListBasePreferenceController.java
+++ b/src/com/android/settings/regionalpreferences/RegionalPreferenceListBasePreferenceController.java
@@ -22,16 +22,20 @@
import androidx.preference.PreferenceScreen;
import com.android.settings.core.BasePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.TickButtonPreference;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
/** A base controller for handling all regional preferences controllers. */
public abstract class RegionalPreferenceListBasePreferenceController extends
BasePreferenceController {
+ private final MetricsFeatureProvider mMetricsFeatureProvider;
private PreferenceCategory mPreferenceCategory;
public RegionalPreferenceListBasePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
+ mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
}
@Override
@@ -61,6 +65,8 @@
RegionalPreferencesDataUtils.savePreference(mContext, getExtensionTypes(),
item.equals(RegionalPreferencesDataUtils.DEFAULT_VALUE)
? null : item);
+ mMetricsFeatureProvider.action(mContext, getMetricsActionKey(),
+ getPreferenceTitle(value) + " > " + getPreferenceTitle(item));
return true;
});
pref.setSelected(!value.isEmpty() && item.equals(value));
@@ -90,4 +96,8 @@
protected abstract String getExtensionTypes();
protected abstract String[] getUnitValues();
+
+ protected abstract int getMetricsActionKey();
+
+
}
diff --git a/src/com/android/settings/regionalpreferences/TemperatureUnitListController.java b/src/com/android/settings/regionalpreferences/TemperatureUnitListController.java
index c51ca71..91ab1a2 100644
--- a/src/com/android/settings/regionalpreferences/TemperatureUnitListController.java
+++ b/src/com/android/settings/regionalpreferences/TemperatureUnitListController.java
@@ -16,6 +16,7 @@
package com.android.settings.regionalpreferences;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import com.android.settings.R;
@@ -55,4 +56,9 @@
protected String[] getUnitValues() {
return mContext.getResources().getStringArray(R.array.temperature_units);
}
+
+ @Override
+ protected int getMetricsActionKey() {
+ return SettingsEnums.ACTION_SET_TEMPERATURE_UNIT;
+ }
}
diff --git a/src/com/android/settings/spa/app/appinfo/AppButtons.kt b/src/com/android/settings/spa/app/appinfo/AppButtons.kt
index 3200b81..307ff11 100644
--- a/src/com/android/settings/spa/app/appinfo/AppButtons.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppButtons.kt
@@ -17,6 +17,8 @@
package com.android.settings.spa.app.appinfo
import android.content.pm.ApplicationInfo
+import android.content.pm.FeatureFlags
+import android.content.pm.FeatureFlagsImpl
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -25,16 +27,22 @@
import com.android.settingslib.spa.widget.button.ActionButtons
@Composable
-fun AppButtons(packageInfoPresenter: PackageInfoPresenter) {
+/**
+ * @param featureFlags can be overridden in tests
+ */
+fun AppButtons(packageInfoPresenter: PackageInfoPresenter, featureFlags: FeatureFlags = FeatureFlagsImpl()) {
if (remember(packageInfoPresenter) { packageInfoPresenter.isMainlineModule() }) return
- val presenter = remember { AppButtonsPresenter(packageInfoPresenter) }
+ val presenter = remember { AppButtonsPresenter(packageInfoPresenter, featureFlags) }
ActionButtons(actionButtons = presenter.getActionButtons())
}
private fun PackageInfoPresenter.isMainlineModule(): Boolean =
AppUtils.isMainlineModule(userPackageManager, packageName)
-private class AppButtonsPresenter(private val packageInfoPresenter: PackageInfoPresenter) {
+private class AppButtonsPresenter(
+ private val packageInfoPresenter: PackageInfoPresenter,
+ private val featureFlags: FeatureFlags
+) {
private val appLaunchButton = AppLaunchButton(packageInfoPresenter)
private val appInstallButton = AppInstallButton(packageInfoPresenter)
private val appDisableButton = AppDisableButton(packageInfoPresenter)
@@ -50,7 +58,7 @@
@Composable
private fun getActionButtons(app: ApplicationInfo): List<ActionButton> = listOfNotNull(
- appLaunchButton.getActionButton(app),
+ if (featureFlags.archiving()) null else appLaunchButton.getActionButton(app),
appInstallButton.getActionButton(app),
appDisableButton.getActionButton(app),
appUninstallButton.getActionButton(app),
diff --git a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
index a9d16ae..ed912c3 100644
--- a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
@@ -18,6 +18,8 @@
import android.app.settings.SettingsEnums
import android.content.pm.ApplicationInfo
+import android.content.pm.FeatureFlags
+import android.content.pm.FeatureFlagsImpl
import android.os.Bundle
import android.os.UserHandle
import android.util.FeatureFlagUtils
@@ -119,9 +121,11 @@
LifecycleEffect(onStart = { packageInfoPresenter.reloadPackageInfo() })
val packageInfo = packageInfoPresenter.flow.collectAsStateWithLifecycle().value ?: return
val app = checkNotNull(packageInfo.applicationInfo)
+ val featureFlags: FeatureFlags = FeatureFlagsImpl()
RegularScaffold(
title = stringResource(R.string.application_info_label),
actions = {
+ if (featureFlags.archiving()) TopBarAppLaunchButton(packageInfoPresenter, app)
AppInfoSettingsMoreOptions(packageInfoPresenter, app)
}
) {
diff --git a/src/com/android/settings/spa/app/appinfo/TopBarAppLaunchButton.kt b/src/com/android/settings/spa/app/appinfo/TopBarAppLaunchButton.kt
new file mode 100644
index 0000000..92ad139
--- /dev/null
+++ b/src/com/android/settings/spa/app/appinfo/TopBarAppLaunchButton.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.spa.app.appinfo
+
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.Launch
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import com.android.settings.R
+import com.android.settingslib.spaprivileged.model.app.userHandle
+
+@Composable
+fun TopBarAppLaunchButton(packageInfoPresenter: PackageInfoPresenter, app: ApplicationInfo) {
+ val intent = packageInfoPresenter.launchIntent(app = app) ?: return
+ IconButton({ launchButtonAction(intent, app, packageInfoPresenter) }) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Outlined.Launch,
+ contentDescription = stringResource(R.string.launch_instant_app),
+ )
+ }
+}
+
+private fun PackageInfoPresenter.launchIntent(
+ app: ApplicationInfo
+): Intent? {
+ return userPackageManager.getLaunchIntentForPackage(app.packageName)
+}
+
+private fun launchButtonAction(
+ intent: Intent,
+ app: ApplicationInfo,
+ packageInfoPresenter: PackageInfoPresenter
+) {
+ try {
+ packageInfoPresenter.context.startActivityAsUser(intent, app.userHandle)
+ } catch (_: ActivityNotFoundException) {
+ // Only happens after package changes like uninstall, and before page auto refresh or
+ // close, so ignore this exception is safe.
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/utils/SettingsGlobalBooleanDelegate.kt b/src/com/android/settings/utils/SettingsGlobalBooleanDelegate.kt
new file mode 100644
index 0000000..fdfbdb4
--- /dev/null
+++ b/src/com/android/settings/utils/SettingsGlobalBooleanDelegate.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.utils
+
+import android.content.ContentResolver
+import android.content.Context
+import android.database.ContentObserver
+import android.os.Handler
+import android.provider.Settings
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KProperty
+
+fun Context.observeSettingsGlobalBoolean(
+ name: String,
+ lifecycle: Lifecycle,
+ onChange: (newValue: Boolean) -> Unit,
+) {
+ val field by settingsGlobalBoolean(name)
+ val contentObserver = object : ContentObserver(Handler.getMain()) {
+ override fun onChange(selfChange: Boolean) {
+ onChange(field)
+ }
+ }
+ val uri = Settings.Global.getUriFor(name)
+ lifecycle.addObserver(object : DefaultLifecycleObserver {
+ override fun onStart(owner: LifecycleOwner) {
+ contentResolver.registerContentObserver(uri, false, contentObserver)
+ onChange(field)
+ }
+
+ override fun onStop(owner: LifecycleOwner) {
+ contentResolver.unregisterContentObserver(contentObserver)
+ }
+ })
+}
+
+fun Context.settingsGlobalBoolean(name: String): ReadWriteProperty<Any?, Boolean> =
+ SettingsGlobalBooleanDelegate(this, name)
+
+private class SettingsGlobalBooleanDelegate(context: Context, private val name: String) :
+ ReadWriteProperty<Any?, Boolean> {
+
+ private val contentResolver: ContentResolver = context.contentResolver
+
+ override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean =
+ Settings.Global.getInt(contentResolver, name, 0) != 0
+
+ override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) {
+ Settings.Global.putInt(contentResolver, name, if (value) 1 else 0)
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint2/fragment/FingerprintEnrollIntroFragmentTest.kt b/tests/robotests/src/com/android/settings/biometrics/fingerprint2/fragment/FingerprintEnrollIntroFragmentTest.kt
index cea6676..024f346 100644
--- a/tests/robotests/src/com/android/settings/biometrics/fingerprint2/fragment/FingerprintEnrollIntroFragmentTest.kt
+++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint2/fragment/FingerprintEnrollIntroFragmentTest.kt
@@ -33,12 +33,15 @@
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.runner.AndroidJUnit4
import com.android.settings.R
+import com.android.settings.biometrics.fingerprint2.shared.model.Default
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollIntroV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.GatekeeperInfo
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Intro
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.NavState
import com.android.settings.testutils2.FakeFingerprintManagerInteractor
import com.google.android.setupdesign.GlifLayout
import com.google.android.setupdesign.template.RequireScrollMixin
@@ -65,9 +68,12 @@
backgroundDispatcher,
interactor,
gatekeeperViewModel,
- canSkipConfirm = true,
+ Intro,
+ NavState(true),
+ Default,
)
- private var fingerprintViewModel = FingerprintEnrollViewModel(interactor, backgroundDispatcher)
+ private var fingerprintViewModel =
+ FingerprintEnrollViewModel(interactor, gatekeeperViewModel, navigationViewModel)
private var fingerprintScrollViewModel = FingerprintScrollViewModel()
@Before
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java
index f43feec..bbba294 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java
@@ -352,16 +352,18 @@
}
@Test
- public void testClearCache_clearDataForResourcesAndFlags() {
+ public void testClearCache_clearDataForAllCaches() {
BatteryDiffEntry.sResourceCache.put(
"fake application key",
new BatteryEntry.NameAndIcon("app label", null, /* iconId= */ 0));
BatteryDiffEntry.sValidForRestriction.put("fake application key", Boolean.valueOf(false));
+ BatteryDiffEntry.sPackageNameAndUidCache.put(PACKAGE_NAME, UID);
BatteryDiffEntry.clearCache();
assertThat(BatteryDiffEntry.sResourceCache).isEmpty();
assertThat(BatteryDiffEntry.sValidForRestriction).isEmpty();
+ assertThat(BatteryDiffEntry.sPackageNameAndUidCache).isEmpty();
}
@Test
@@ -445,7 +447,11 @@
final BatteryDiffEntry entry = createBatteryDiffEntry(10, new BatteryHistEntry(values));
assertThat(entry.isSystemEntry()).isFalse();
+ assertThat(BatteryDiffEntry.sPackageNameAndUidCache.containsKey(PACKAGE_NAME)).isFalse();
assertThat(entry.isUninstalledEntry()).isFalse();
+ assertThat(BatteryDiffEntry.sPackageNameAndUidCache.containsKey(PACKAGE_NAME)).isTrue();
+ assertThat(BatteryDiffEntry.sPackageNameAndUidCache.get(PACKAGE_NAME)).isEqualTo(UID);
+
}
@Test
@@ -457,7 +463,9 @@
final BatteryDiffEntry entry = createBatteryDiffEntry(10, new BatteryHistEntry(values));
assertThat(entry.isSystemEntry()).isFalse();
+ assertThat(BatteryDiffEntry.sPackageNameAndUidCache.containsKey(PACKAGE_NAME)).isFalse();
assertThat(entry.isUninstalledEntry()).isFalse();
+ assertThat(BatteryDiffEntry.sPackageNameAndUidCache.containsKey(PACKAGE_NAME)).isFalse();
}
@Test
@@ -469,7 +477,11 @@
final BatteryDiffEntry entry = createBatteryDiffEntry(10, new BatteryHistEntry(values));
assertThat(entry.isSystemEntry()).isFalse();
+ assertThat(BatteryDiffEntry.sPackageNameAndUidCache.containsKey(UNINSTALLED_PACKAGE_NAME))
+ .isFalse();
assertThat(entry.isUninstalledEntry()).isTrue();
+ assertThat(BatteryDiffEntry.sPackageNameAndUidCache.get(UNINSTALLED_PACKAGE_NAME))
+ .isEqualTo(BatteryUtils.UID_NULL);
}
@Test
@@ -590,7 +602,7 @@
final BatteryHistEntry batteryHistEntry = new BatteryHistEntry(values);
doReturn(drawable).when(mMockPackageManager).getDefaultActivityIcon();
doReturn(null).when(mMockPackageManager).getApplicationInfo("com.a.b.c", 0);
- doReturn(new String[] {"com.a.b.c"}).when(mMockPackageManager).getPackagesForUid(1001);
+ doReturn(new String[]{"com.a.b.c"}).when(mMockPackageManager).getPackagesForUid(1001);
return createBatteryDiffEntry(10, batteryHistEntry);
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManagerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManagerTest.java
new file mode 100644
index 0000000..cdf1514
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManagerTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.datasaver;
+
+import static android.net.NetworkPolicyManager.POLICY_NONE;
+import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class DynamicDenylistManagerTest {
+
+ private static final String FAKE_UID_1 = "package_uid_1";
+ private static final String FAKE_UID_2 = "package_uid_2";
+
+ private SharedPreferences mManualDenyListPref;
+ private SharedPreferences mDynamicDenyListPref;
+ private DynamicDenylistManager mDynamicDenylistManager;
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application.getApplicationContext();
+ mDynamicDenylistManager = new DynamicDenylistManager(mContext);
+ mManualDenyListPref = mDynamicDenylistManager.getManualDenylistPref();
+ mDynamicDenyListPref = mDynamicDenylistManager.getDynamicDenylistPref();
+ }
+
+ @After
+ public void tearDown() {
+ mDynamicDenylistManager.clearManualDenylistPref();
+ mDynamicDenylistManager.clearDynamicDenylistPref();
+ }
+
+ @Test
+ public void getManualDenylistPref_isEmpty() {
+ assertThat(mManualDenyListPref.getAll()).isEmpty();
+ }
+
+ @Test
+ public void getDynamicDenylistPref_isEmpty() {
+ assertThat(mDynamicDenyListPref.getAll()).isEmpty();
+ }
+
+ @Test
+ public void getManualDenylistPref_initiated_containsExpectedValue() {
+ mManualDenyListPref.edit().putInt(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND).apply();
+
+ assertThat(mManualDenyListPref.getAll().size()).isEqualTo(1);
+ assertTrue(mManualDenyListPref.contains(FAKE_UID_1));
+ }
+
+ @Test
+ public void getDynamicDenylistPref_initiated_containsExpectedValue() {
+ mDynamicDenyListPref.edit().putInt(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND).apply();
+
+ assertThat(mDynamicDenyListPref.getAll()).hasSize(1);
+ assertTrue(mDynamicDenyListPref.contains(FAKE_UID_1));
+ }
+
+ @Test
+ public void updateManualDenylist_policyReject_addsUid() {
+ mDynamicDenylistManager.updateManualDenylist(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND);
+
+ assertThat(mManualDenyListPref.getAll()).hasSize(1);
+ assertTrue(mManualDenyListPref.contains(FAKE_UID_1));
+ }
+
+ @Test
+ public void updateManualDenylist_policyNone_removesUid() {
+ mManualDenyListPref.edit().putInt(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND).apply();
+ assertTrue(mManualDenyListPref.contains(FAKE_UID_1));
+
+ mDynamicDenylistManager.updateManualDenylist(FAKE_UID_1, POLICY_NONE);
+
+ assertThat(mManualDenyListPref.getAll()).isEmpty();
+ }
+
+ @Test
+ public void updateManualDenylist_samePolicy_doNothing() {
+ mManualDenyListPref.edit().putInt(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND).apply();
+ assertTrue(mManualDenyListPref.contains(FAKE_UID_1));
+
+ mDynamicDenylistManager.updateManualDenylist(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND);
+
+ assertThat(mManualDenyListPref.getAll()).hasSize(1);
+ }
+
+ @Test
+ public void isManualDenylist_returnsFalse() {
+ assertFalse(mDynamicDenylistManager.isInManualDenylist(FAKE_UID_1));
+ }
+
+ @Test
+ public void isManualDenylist_incorrectUid_returnsFalse() {
+ mManualDenyListPref.edit().putInt(FAKE_UID_2, POLICY_REJECT_METERED_BACKGROUND).apply();
+
+ assertFalse(mDynamicDenylistManager.isInManualDenylist(FAKE_UID_1));
+ }
+
+ @Test
+ public void isManualDenylist_initiated_returnsTrue() {
+ mManualDenyListPref.edit().putInt(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND).apply();
+
+ assertTrue(mDynamicDenylistManager.isInManualDenylist(FAKE_UID_1));
+ }
+
+ @Test
+ public void clearManualDenylistPref_isEmpty() {
+ mManualDenyListPref.edit().putInt(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND).apply();
+ assertThat(mManualDenyListPref.getAll()).hasSize(1);
+ assertTrue(mManualDenyListPref.contains(FAKE_UID_1));
+
+ mDynamicDenylistManager.clearManualDenylistPref();
+
+ assertThat(mManualDenyListPref.getAll()).isEmpty();
+ }
+
+ @Test
+ public void clearDynamicDenylistPref_isEmpty() {
+ mDynamicDenyListPref.edit().putInt(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND).apply();
+ assertThat(mDynamicDenyListPref.getAll()).hasSize(1);
+ assertTrue(mDynamicDenyListPref.contains(FAKE_UID_1));
+
+ mDynamicDenylistManager.clearDynamicDenylistPref();
+
+ assertThat(mDynamicDenyListPref.getAll()).isEmpty();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/network/MobileNetworkListFragmentTest.java b/tests/robotests/src/com/android/settings/network/MobileNetworkListFragmentTest.java
deleted file mode 100644
index 2e04ea7..0000000
--- a/tests/robotests/src/com/android/settings/network/MobileNetworkListFragmentTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.network;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.os.UserManager;
-
-import com.android.settings.R;
-import com.android.settings.search.BaseSearchIndexProvider;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.util.ReflectionHelpers;
-
-@RunWith(RobolectricTestRunner.class)
-public class MobileNetworkListFragmentTest {
- @Mock
- private Context mContext;
- @Mock
- private Resources mResources;
- @Mock
- private UserManager mUserManager;
-
- private MobileNetworkListFragment mFragment;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mFragment = new MobileNetworkListFragment();
- }
-
- @Test
- public void isPageSearchEnabled_adminUser_shouldReturnTrue() {
- when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
- when(mUserManager.isAdminUser()).thenReturn(true);
- final BaseSearchIndexProvider provider =
- (BaseSearchIndexProvider) mFragment.SEARCH_INDEX_DATA_PROVIDER;
-
- when(mContext.getResources()).thenReturn(mResources);
- when(mResources.getBoolean(R.bool.config_show_sim_info)).thenReturn(true);
-
- final Object obj = ReflectionHelpers.callInstanceMethod(provider, "isPageSearchEnabled",
- ReflectionHelpers.ClassParameter.from(Context.class, mContext));
- final boolean isEnabled = (Boolean) obj;
-
- assertThat(isEnabled).isTrue();
- }
-
- @Test
- public void isPageSearchEnabled_nonAdminUser_shouldReturnFalse() {
- when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
- when(mUserManager.isAdminUser()).thenReturn(false);
- final BaseSearchIndexProvider provider =
- (BaseSearchIndexProvider) mFragment.SEARCH_INDEX_DATA_PROVIDER;
-
- when(mContext.getResources()).thenReturn(mResources);
- when(mResources.getBoolean(R.bool.config_show_sim_info)).thenReturn(true);
-
- final Object obj = ReflectionHelpers.callInstanceMethod(provider, "isPageSearchEnabled",
- ReflectionHelpers.ClassParameter.from(Context.class, mContext));
- final boolean isEnabled = (Boolean) obj;
-
- assertThat(isEnabled).isFalse();
- }
-}
diff --git a/tests/shared/src/com/android/settings/testutils2/FakeFingerprintManagerInteractor.kt b/tests/shared/src/com/android/settings/testutils2/FakeFingerprintManagerInteractor.kt
index ad943f2..dd8658c 100644
--- a/tests/shared/src/com/android/settings/testutils2/FakeFingerprintManagerInteractor.kt
+++ b/tests/shared/src/com/android/settings/testutils2/FakeFingerprintManagerInteractor.kt
@@ -18,9 +18,9 @@
import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
import com.android.systemui.biometrics.shared.model.FingerprintSensor
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
@@ -32,10 +32,11 @@
class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
var enrollableFingerprints: Int = 5
- var enrolledFingerprintsInternal: MutableList<FingerprintViewModel> = mutableListOf()
+ var enrolledFingerprintsInternal: MutableList<FingerprintData> = mutableListOf()
var challengeToGenerate: Pair<Long, ByteArray> = Pair(-1L, byteArrayOf())
- var authenticateAttempt = FingerprintAuthAttemptViewModel.Success(1)
- val enrollStateViewModel = FingerEnrollStateViewModel.EnrollProgress(1)
+ var authenticateAttempt = FingerprintAuthAttemptModel.Success(1)
+ var enrollStateViewModel: List<FingerEnrollState> =
+ listOf(FingerEnrollState.EnrollProgress(5, 5))
var pressToAuthEnabled = true
var sensorProp =
@@ -46,7 +47,7 @@
FingerprintSensorType.POWER_BUTTON
)
- override suspend fun authenticate(): FingerprintAuthAttemptViewModel {
+ override suspend fun authenticate(): FingerprintAuthAttemptModel {
return authenticateAttempt
}
@@ -54,7 +55,7 @@
return challengeToGenerate
}
- override val enrolledFingerprints: Flow<List<FingerprintViewModel>> = flow {
+ override val enrolledFingerprints: Flow<List<FingerprintData>> = flow {
emit(enrolledFingerprintsInternal)
}
@@ -62,24 +63,22 @@
emit(enrolledFingerprintsInternal.size < enrollableFingerprints)
}
- override val sensorPropertiesInternal: Flow<FingerprintSensor?> = flow {
- emit(sensorProp)
- }
+ override val sensorPropertiesInternal: Flow<FingerprintSensor?> = flow { emit(sensorProp) }
override val maxEnrollableFingerprints: Flow<Int> = flow { emit(enrollableFingerprints) }
override suspend fun enroll(
hardwareAuthToken: ByteArray?,
enrollReason: EnrollReason
- ): Flow<FingerEnrollStateViewModel> = flowOf(enrollStateViewModel)
+ ): Flow<FingerEnrollState> = flowOf(*enrollStateViewModel.toTypedArray())
- override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean {
+ override suspend fun removeFingerprint(fp: FingerprintData): Boolean {
return enrolledFingerprintsInternal.remove(fp)
}
- override suspend fun renameFingerprint(fp: FingerprintViewModel, newName: String) {
+ override suspend fun renameFingerprint(fp: FingerprintData, newName: String) {
if (enrolledFingerprintsInternal.remove(fp)) {
- enrolledFingerprintsInternal.add(FingerprintViewModel(newName, fp.fingerId, fp.deviceId))
+ enrolledFingerprintsInternal.add(FingerprintData(newName, fp.fingerId, fp.deviceId))
}
}
diff --git a/tests/spa_unit/src/com/android/settings/network/MobileNetworkListFragmentTest.kt b/tests/spa_unit/src/com/android/settings/network/MobileNetworkListFragmentTest.kt
new file mode 100644
index 0000000..3ba4bac
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/MobileNetworkListFragmentTest.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network
+
+import android.content.Context
+import android.content.res.Resources
+import android.os.UserManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.android.settingslib.spaprivileged.framework.common.userManager
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class MobileNetworkListFragmentTest {
+ private val mockUserManager = mock<UserManager>()
+
+ private val mockResources = mock<Resources>()
+
+ private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+ on { userManager } doReturn mockUserManager
+ on { resources } doReturn mockResources
+ }
+
+ @Test
+ fun isPageSearchEnabled_adminUser_shouldReturnTrue() {
+ mockUserManager.stub {
+ on { isAdminUser } doReturn true
+ }
+ mockResources.stub {
+ on { getBoolean(R.bool.config_show_sim_info) } doReturn true
+ }
+
+ val isEnabled =
+ MobileNetworkListFragment.SEARCH_INDEX_DATA_PROVIDER.isPageSearchEnabled(context)
+
+ assertThat(isEnabled).isTrue()
+ }
+
+ @Test
+ fun isPageSearchEnabled_nonAdminUser_shouldReturnFalse() {
+ mockUserManager.stub {
+ on { isAdminUser } doReturn false
+ }
+ mockResources.stub {
+ on { getBoolean(R.bool.config_show_sim_info) } doReturn true
+ }
+
+ val isEnabled =
+ MobileNetworkListFragment.SEARCH_INDEX_DATA_PROVIDER.isPageSearchEnabled(context)
+
+ assertThat(isEnabled).isFalse()
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
index 8faf5c9..e2f55ef 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
@@ -17,16 +17,21 @@
package com.android.settings.spa.app.appinfo
import android.content.Context
+import android.content.Intent
import android.content.pm.ApplicationInfo
+import android.content.pm.FakeFeatureFlagsImpl
+import android.content.pm.Flags
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.onRoot
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.settings.R
import com.android.settingslib.applications.AppUtils
import com.android.settingslib.spa.testutils.delay
import kotlinx.coroutines.flow.MutableStateFlow
@@ -57,6 +62,8 @@
@Mock
private lateinit var packageManager: PackageManager
+ private val featureFlags = FakeFeatureFlagsImpl()
+
@Before
fun setUp() {
mockSession = ExtendedMockito.mockitoSession()
@@ -69,6 +76,7 @@
whenever(packageInfoPresenter.userPackageManager).thenReturn(packageManager)
whenever(packageManager.getPackageInfo(PACKAGE_NAME, 0)).thenReturn(PACKAGE_INFO)
whenever(AppUtils.isMainlineModule(packageManager, PACKAGE_NAME)).thenReturn(false)
+ featureFlags.setFlag(Flags.FLAG_ARCHIVING, true)
}
@After
@@ -92,10 +100,28 @@
composeTestRule.onRoot().assertIsDisplayed()
}
+ @Test
+ fun launchButton_displayed_archivingDisabled() {
+ whenever(packageManager.getLaunchIntentForPackage(PACKAGE_NAME)).thenReturn(Intent())
+ featureFlags.setFlag(Flags.FLAG_ARCHIVING, false)
+ setContent()
+
+ composeTestRule.onNodeWithText(context.getString(R.string.launch_instant_app)).assertIsDisplayed()
+ }
+
+ @Test
+ fun launchButton_notDisplayed_archivingEnabled() {
+ whenever(packageManager.getLaunchIntentForPackage(PACKAGE_NAME)).thenReturn(Intent())
+ featureFlags.setFlag(Flags.FLAG_ARCHIVING, true)
+ setContent()
+
+ composeTestRule.onNodeWithText(context.getString(R.string.launch_instant_app)).assertIsNotDisplayed()
+ }
+
private fun setContent() {
whenever(packageInfoPresenter.flow).thenReturn(MutableStateFlow(PACKAGE_INFO))
composeTestRule.setContent {
- AppButtons(packageInfoPresenter)
+ AppButtons(packageInfoPresenter, featureFlags)
}
composeTestRule.delay()
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/TopBarAppLaunchButtonTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/TopBarAppLaunchButtonTest.kt
new file mode 100644
index 0000000..7b54247
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/TopBarAppLaunchButtonTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.spa.app.appinfo
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.hasContentDescription
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.settings.R
+import com.android.settingslib.spa.testutils.waitUntilExists
+import com.android.settingslib.spaprivileged.model.app.userHandle
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoSession
+import org.mockito.Spy
+import org.mockito.quality.Strictness
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class TopBarAppLaunchButtonTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private lateinit var mockSession: MockitoSession
+
+ @Spy
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Mock
+ private lateinit var packageInfoPresenter: PackageInfoPresenter
+
+ @Mock
+ private lateinit var userPackageManager: PackageManager
+
+ @Before
+ fun setUp() {
+ mockSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .startMocking()
+ whenever(packageInfoPresenter.context).thenReturn(context)
+ whenever(packageInfoPresenter.userPackageManager).thenReturn(userPackageManager)
+ val intent = Intent()
+ whenever(userPackageManager.getLaunchIntentForPackage(PACKAGE_NAME)).thenReturn(intent)
+ }
+
+ @After
+ fun tearDown() {
+ mockSession.finishMocking()
+ }
+
+ @Test
+ fun topBarAppLaunchButton_isDisplayed() {
+ val app = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ }
+
+ setContent(app)
+
+ composeTestRule.waitUntilExists(
+ hasContentDescription(context.getString(R.string.launch_instant_app))
+ )
+ }
+
+ @Test
+ fun topBarAppLaunchButton_opensApp() {
+ val app = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ }
+
+ setContent(app)
+ composeTestRule.onNodeWithContentDescription(context.getString(R.string.launch_instant_app))
+ .performClick()
+
+ verify(context).startActivityAsUser(any(), eq(app.userHandle))
+ }
+
+ private fun setContent(app: ApplicationInfo) {
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ TopBarAppLaunchButton(packageInfoPresenter, app)
+ }
+ }
+ }
+
+ private companion object {
+ const val PACKAGE_NAME = "package.name"
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/utils/SettingsGlobalBooleanDelegateTest.kt b/tests/spa_unit/src/com/android/settings/utils/SettingsGlobalBooleanDelegateTest.kt
new file mode 100644
index 0000000..75c3685
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/utils/SettingsGlobalBooleanDelegateTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.utils
+
+import android.content.Context
+import android.provider.Settings
+import androidx.lifecycle.testing.TestLifecycleOwner
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsGlobalBooleanDelegateTest {
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun getValue_setTrue_returnTrue() {
+ Settings.Global.putInt(context.contentResolver, TEST_NAME, 1)
+
+ val value by context.settingsGlobalBoolean(TEST_NAME)
+
+ assertThat(value).isTrue()
+ }
+
+ @Test
+ fun getValue_setFalse_returnFalse() {
+ Settings.Global.putInt(context.contentResolver, TEST_NAME, 0)
+
+ val value by context.settingsGlobalBoolean(TEST_NAME)
+
+ assertThat(value).isFalse()
+ }
+
+ @Test
+ fun setValue_setTrue_returnTrue() {
+ var value by context.settingsGlobalBoolean(TEST_NAME)
+
+ value = true
+
+ assertThat(Settings.Global.getInt(context.contentResolver, TEST_NAME, 0)).isEqualTo(1)
+ }
+
+ @Test
+ fun setValue_setFalse_returnFalse() {
+ var value by context.settingsGlobalBoolean(TEST_NAME)
+
+ value = false
+
+ assertThat(Settings.Global.getInt(context.contentResolver, TEST_NAME, 1)).isEqualTo(0)
+ }
+
+ @Test
+ fun observeSettingsGlobalBoolean_valueNotChanged() {
+ var value by context.settingsGlobalBoolean(TEST_NAME)
+ value = false
+ var newValue: Boolean? = null
+
+ context.observeSettingsGlobalBoolean(TEST_NAME, TestLifecycleOwner().lifecycle) {
+ newValue = it
+ }
+
+ assertThat(newValue).isFalse()
+ }
+
+ @Test
+ fun observeSettingsGlobalBoolean_valueChanged() {
+ var value by context.settingsGlobalBoolean(TEST_NAME)
+ value = false
+ var newValue: Boolean? = null
+
+ context.observeSettingsGlobalBoolean(TEST_NAME, TestLifecycleOwner().lifecycle) {
+ newValue = it
+ }
+ value = true
+
+ assertThat(newValue).isFalse()
+ }
+
+ private companion object {
+ const val TEST_NAME = "test_boolean_delegate"
+ }
+}
diff --git a/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt b/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt
index f0d0a0a..3440d2a 100644
--- a/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt
+++ b/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt
@@ -26,12 +26,14 @@
import android.os.Handler
import androidx.test.core.app.ApplicationProvider
import com.android.settings.biometrics.GatekeeperPasswordProvider
-import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
+import com.android.settings.biometrics.fingerprint2.shared.data.repository.PressToAuthProvider
+import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.shared.model.Default
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
import com.android.settings.password.ChooseLockSettingsHelper
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.cancelAndJoin
@@ -69,7 +71,11 @@
@Mock private lateinit var gateKeeperPasswordProvider: GatekeeperPasswordProvider
private var testScope = TestScope(backgroundDispatcher)
- private var pressToAuthProvider = { true }
+ private var pressToAuthProvider =
+ object : PressToAuthProvider {
+ override val isEnabled: Boolean
+ get() = false
+ }
@Before
fun setup() {
@@ -80,6 +86,7 @@
fingerprintManager,
gateKeeperPasswordProvider,
pressToAuthProvider,
+ Default,
)
}
@@ -164,7 +171,7 @@
@Test
fun testRemoveFingerprint_succeeds() =
testScope.runTest {
- val fingerprintViewModelToRemove = FingerprintViewModel("Finger 2", 1, 2L)
+ val fingerprintViewModelToRemove = FingerprintData("Finger 2", 1, 2L)
val fingerprintToRemove = Fingerprint("Finger 2", 1, 2L)
val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> = argumentCaptor()
@@ -187,7 +194,7 @@
@Test
fun testRemoveFingerprint_fails() =
testScope.runTest {
- val fingerprintViewModelToRemove = FingerprintViewModel("Finger 2", 1, 2L)
+ val fingerprintViewModelToRemove = FingerprintData("Finger 2", 1, 2L)
val fingerprintToRemove = Fingerprint("Finger 2", 1, 2L)
val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> = argumentCaptor()
@@ -214,7 +221,7 @@
@Test
fun testRenameFingerprint_succeeds() =
testScope.runTest {
- val fingerprintToRename = FingerprintViewModel("Finger 2", 1, 2L)
+ val fingerprintToRename = FingerprintData("Finger 2", 1, 2L)
underTest.renameFingerprint(fingerprintToRename, "Woo")
@@ -226,7 +233,7 @@
testScope.runTest {
val fingerprint = Fingerprint("Woooo", 100, 101L)
- var result: FingerprintAuthAttemptViewModel? = null
+ var result: FingerprintAuthAttemptModel? = null
val job = launch { result = underTest.authenticate() }
val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> = argumentCaptor()
@@ -247,13 +254,13 @@
runCurrent()
job.cancelAndJoin()
- assertThat(result).isEqualTo(FingerprintAuthAttemptViewModel.Success(fingerprint.biometricId))
+ assertThat(result).isEqualTo(FingerprintAuthAttemptModel.Success(fingerprint.biometricId))
}
@Test
fun testAuth_lockout() =
testScope.runTest {
- var result: FingerprintAuthAttemptViewModel? = null
+ var result: FingerprintAuthAttemptModel? = null
val job = launch { result = underTest.authenticate() }
val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> = argumentCaptor()
@@ -274,7 +281,7 @@
job.cancelAndJoin()
assertThat(result)
.isEqualTo(
- FingerprintAuthAttemptViewModel.Error(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "Lockout!!")
+ FingerprintAuthAttemptModel.Error(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "Lockout!!")
)
}
@@ -282,7 +289,7 @@
fun testEnroll_progress() =
testScope.runTest {
val token = byteArrayOf(5, 3, 2)
- var result: FingerEnrollStateViewModel? = null
+ var result: FingerEnrollState? = null
val job = launch { underTest.enroll(token, EnrollReason.FindSensor).collect { result = it } }
val enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor()
runCurrent()
@@ -299,14 +306,14 @@
runCurrent()
job.cancelAndJoin()
- assertThat(result).isEqualTo(FingerEnrollStateViewModel.EnrollProgress(1))
+ assertThat(result).isEqualTo(FingerEnrollState.EnrollProgress(1, 2))
}
@Test
fun testEnroll_help() =
testScope.runTest {
val token = byteArrayOf(5, 3, 2)
- var result: FingerEnrollStateViewModel? = null
+ var result: FingerEnrollState? = null
val job = launch { underTest.enroll(token, EnrollReason.FindSensor).collect { result = it } }
val enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor()
runCurrent()
@@ -323,14 +330,14 @@
runCurrent()
job.cancelAndJoin()
- assertThat(result).isEqualTo(FingerEnrollStateViewModel.EnrollHelp(-1, "help"))
+ assertThat(result).isEqualTo(FingerEnrollState.EnrollHelp(-1, "help"))
}
@Test
fun testEnroll_error() =
testScope.runTest {
val token = byteArrayOf(5, 3, 2)
- var result: FingerEnrollStateViewModel? = null
+ var result: FingerEnrollState? = null
val job = launch { underTest.enroll(token, EnrollReason.FindSensor).collect { result = it } }
val enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor()
runCurrent()
@@ -343,17 +350,20 @@
capture(enrollCallback),
eq(FingerprintManager.ENROLL_FIND_SENSOR)
)
- enrollCallback.value.onEnrollmentError(-2, "error")
+ enrollCallback.value.onEnrollmentError(-1, "error")
runCurrent()
job.cancelAndJoin()
-
- assertThat(result).isEqualTo(FingerEnrollStateViewModel.EnrollError(-2, "error"))
+ assertThat(result).isInstanceOf(FingerEnrollState.EnrollError::class.java)
}
private fun <T : Any> safeEq(value: T): T = eq(value) ?: value
+
private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+
private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+
private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
+
inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> =
ArgumentCaptor.forClass(T::class.java)
}
diff --git a/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt b/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt
index 509b0ed..bd94cba 100644
--- a/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt
+++ b/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt
@@ -21,7 +21,9 @@
import android.view.accessibility.AccessibilityManager
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.core.app.ApplicationProvider
+import com.android.settings.biometrics.fingerprint2.shared.model.Default
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.AccessibilityViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Education
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
@@ -70,6 +72,7 @@
private lateinit var foldStateViewModel: FoldStateViewModel
private lateinit var orientationStateViewModel: OrientationStateViewModel
private lateinit var underTest: FingerprintEnrollFindSensorViewModel
+ private lateinit var backgroundViewModel: BackgroundViewModel
private val context: Context = ApplicationProvider.getApplicationContext()
private val accessibilityManager: AccessibilityManager =
context.getSystemService(AccessibilityManager::class.java)!!
@@ -93,12 +96,18 @@
fakeFingerprintManagerInteractor,
gatekeeperViewModel,
canSkipConfirm = true,
+ Default,
)
.create(FingerprintEnrollNavigationViewModel::class.java)
+
+ backgroundViewModel =
+ BackgroundViewModel.BackgroundViewModelFactory().create(BackgroundViewModel::class.java)
+ backgroundViewModel.inForeground()
enrollViewModel =
FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(
fakeFingerprintManagerInteractor,
- backgroundDispatcher
+ gatekeeperViewModel,
+ navigationViewModel,
)
.create(FingerprintEnrollViewModel::class.java)
accessibilityViewModel =
@@ -114,6 +123,7 @@
navigationViewModel,
enrollViewModel,
gatekeeperViewModel,
+ backgroundViewModel,
accessibilityViewModel,
foldStateViewModel,
orientationStateViewModel
@@ -123,6 +133,7 @@
// Navigate to Education page
navigationViewModel.nextStep()
}
+
@After
fun tearDown() {
Dispatchers.resetMain()
diff --git a/tests/unit/src/com/android/settings/fingerprint2/ui/enrollment/modules/enrolling/rfps/viewmodel/RFPSIconTouchViewModelTest.kt b/tests/unit/src/com/android/settings/fingerprint2/ui/enrollment/modules/enrolling/rfps/viewmodel/RFPSIconTouchViewModelTest.kt
new file mode 100644
index 0000000..46e883a
--- /dev/null
+++ b/tests/unit/src/com/android/settings/fingerprint2/ui/enrollment/modules/enrolling/rfps/viewmodel/RFPSIconTouchViewModelTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fingerprint2.ui.enrollment.modules.enrolling.rfps.viewmodel
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSIconTouchViewModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoJUnitRunner
+
+@RunWith(MockitoJUnitRunner::class)
+class RFPSIconTouchViewModelTest {
+ @JvmField @Rule var rule = MockitoJUnit.rule()
+
+ @get:Rule val instantTaskRule = InstantTaskExecutorRule()
+
+ private var backgroundDispatcher = StandardTestDispatcher()
+ private var testScope = TestScope(backgroundDispatcher)
+ private lateinit var rfpsIconTouchViewModel: RFPSIconTouchViewModel
+
+ @Before
+ fun setup() {
+ Dispatchers.setMain(backgroundDispatcher)
+ testScope = TestScope(backgroundDispatcher)
+ rfpsIconTouchViewModel =
+ RFPSIconTouchViewModel()
+ }
+
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
+ @Test
+ fun initShouldNotShowDialog() =
+ testScope.runTest {
+ var shouldShowDialog = false
+
+ val job = launch { rfpsIconTouchViewModel.shouldShowDialog.collect { shouldShowDialog = it } }
+
+ runCurrent()
+
+ assertThat(shouldShowDialog).isFalse()
+ job.cancel()
+ }
+
+ @Test
+ fun shouldShowDialogTest() =
+ testScope.runTest {
+ var shouldShowDialog = false
+
+ val job = launch { rfpsIconTouchViewModel.shouldShowDialog.collect { shouldShowDialog = it } }
+
+ rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+ rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+ rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+
+ runCurrent()
+
+ assertThat(shouldShowDialog).isTrue()
+ job.cancel()
+ }
+
+ @Test
+ fun stateShouldBeFalseAfterReset() =
+ testScope.runTest {
+ var shouldShowDialog = false
+
+ val job = launch { rfpsIconTouchViewModel.shouldShowDialog.collect { shouldShowDialog = it } }
+
+ rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+ rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+ rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+
+ runCurrent()
+
+ assertThat(shouldShowDialog).isTrue()
+
+ rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+ runCurrent()
+
+ assertThat(shouldShowDialog).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun toggleMultipleTimes() =
+ testScope.runTest {
+ var shouldShowDialog = false
+
+ val job = launch { rfpsIconTouchViewModel.shouldShowDialog.collect { shouldShowDialog = it } }
+
+ rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+ rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+ rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+
+ runCurrent()
+
+ assertThat(shouldShowDialog).isTrue()
+
+ rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+ runCurrent()
+
+ assertThat(shouldShowDialog).isFalse()
+
+ rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+ rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+
+ runCurrent()
+ assertThat(shouldShowDialog).isTrue()
+
+ job.cancel()
+ }
+}
diff --git a/tests/unit/src/com/android/settings/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModelTest.kt b/tests/unit/src/com/android/settings/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModelTest.kt
new file mode 100644
index 0000000..efb4a07
--- /dev/null
+++ b/tests/unit/src/com/android/settings/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModelTest.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fingerprint2.ui.enrollment.viewmodel
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import com.android.settings.biometrics.fingerprint2.shared.model.Default
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Enrollment
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.GatekeeperInfo
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.NavState
+import com.android.settings.testutils2.FakeFingerprintManagerInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoJUnitRunner
+
+@RunWith(MockitoJUnitRunner::class)
+class FingerprintEnrollEnrollingViewModelTest {
+ @JvmField @Rule var rule = MockitoJUnit.rule()
+
+ @get:Rule val instantTaskRule = InstantTaskExecutorRule()
+
+ private var backgroundDispatcher = StandardTestDispatcher()
+ private lateinit var enrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel
+ private lateinit var backgroundViewModel: BackgroundViewModel
+ private lateinit var gateKeeperViewModel: FingerprintGatekeeperViewModel
+ private lateinit var navigationViewModel: FingerprintEnrollNavigationViewModel
+ private val defaultGatekeeperInfo = GatekeeperInfo.GatekeeperPasswordInfo(byteArrayOf(1, 3), 3)
+ private var testScope = TestScope(backgroundDispatcher)
+
+ private lateinit var fakeFingerprintManagerInteractor: FakeFingerprintManagerInteractor
+
+ private fun initialize(gatekeeperInfo: GatekeeperInfo = defaultGatekeeperInfo) {
+ fakeFingerprintManagerInteractor = FakeFingerprintManagerInteractor()
+ gateKeeperViewModel =
+ FingerprintGatekeeperViewModel.FingerprintGatekeeperViewModelFactory(
+ gatekeeperInfo,
+ fakeFingerprintManagerInteractor
+ )
+ .create(FingerprintGatekeeperViewModel::class.java)
+
+ navigationViewModel =
+ FingerprintEnrollNavigationViewModel(
+ backgroundDispatcher,
+ fakeFingerprintManagerInteractor,
+ gateKeeperViewModel,
+ Enrollment,
+ NavState(true),
+ Default,
+ )
+
+ backgroundViewModel =
+ BackgroundViewModel.BackgroundViewModelFactory().create(BackgroundViewModel::class.java)
+ backgroundViewModel.inForeground()
+ val fingerprintEnrollViewModel =
+ FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(
+ fakeFingerprintManagerInteractor,
+ gateKeeperViewModel,
+ navigationViewModel,
+ )
+ .create(FingerprintEnrollViewModel::class.java)
+ enrollEnrollingViewModel =
+ FingerprintEnrollEnrollingViewModel.FingerprintEnrollEnrollingViewModelFactory(
+ fingerprintEnrollViewModel,
+ backgroundViewModel,
+ )
+ .create(FingerprintEnrollEnrollingViewModel::class.java)
+ }
+
+ @Before
+ fun setup() {
+ Dispatchers.setMain(backgroundDispatcher)
+ initialize()
+ }
+
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
+ @Test
+ fun testEnrollShouldBeFalse() =
+ testScope.runTest {
+ var shouldEnroll = false
+
+ val job = launch {
+ enrollEnrollingViewModel.enrollFlowShouldBeRunning.collect { shouldEnroll = it }
+ }
+
+ assertThat(shouldEnroll).isFalse()
+ runCurrent()
+
+ enrollEnrollingViewModel.canEnroll()
+ runCurrent()
+
+ assertThat(shouldEnroll).isTrue()
+ job.cancel()
+ }
+
+ @Test
+ fun testEnrollShouldBeFalseWhenBackground() =
+ testScope.runTest {
+ var shouldEnroll = false
+
+ val job = launch {
+ enrollEnrollingViewModel.enrollFlowShouldBeRunning.collect { shouldEnroll = it }
+ }
+
+ assertThat(shouldEnroll).isFalse()
+ runCurrent()
+
+ enrollEnrollingViewModel.canEnroll()
+ runCurrent()
+
+ assertThat(shouldEnroll).isTrue()
+
+ backgroundViewModel.wentToBackground()
+ runCurrent()
+ assertThat(shouldEnroll).isFalse()
+
+ job.cancel()
+ }
+}
diff --git a/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsNavigationViewModelTest.kt b/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsNavigationViewModelTest.kt
index d4dbec5..064e087 100644
--- a/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsNavigationViewModelTest.kt
+++ b/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsNavigationViewModelTest.kt
@@ -18,7 +18,7 @@
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.android.settings.biometrics.BiometricEnrollBase
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.EnrollFirstFingerprint
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FinishSettings
@@ -208,7 +208,7 @@
fun enrollAdditionalFingerprints_fails() =
testScope.runTest {
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
- mutableListOf(FingerprintViewModel("a", 1, 3L))
+ mutableListOf(FingerprintData("a", 1, 3L))
fakeFingerprintManagerInteractor.challengeToGenerate = Pair(4L, byteArrayOf(3, 3, 1))
var nextStep: NextStepViewModel? = null
@@ -227,7 +227,7 @@
fun enrollAdditional_success() =
testScope.runTest {
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
- mutableListOf(FingerprintViewModel("a", 1, 3L))
+ mutableListOf(FingerprintData("a", 1, 3L))
var nextStep: NextStepViewModel? = null
val job = launch { underTest.nextStep.collect { nextStep = it } }
@@ -245,7 +245,7 @@
fun confirmDeviceCredential_withEnrolledFingerprint_showsSettings() =
testScope.runTest {
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
- mutableListOf(FingerprintViewModel("a", 1, 3L))
+ mutableListOf(FingerprintData("a", 1, 3L))
fakeFingerprintManagerInteractor.challengeToGenerate = Pair(10L, byteArrayOf(1, 2, 3))
var nextStep: NextStepViewModel? = null
@@ -320,7 +320,7 @@
fun showSettings_shouldFinish() =
testScope.runTest {
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
- mutableListOf(FingerprintViewModel("a", 1, 3L))
+ mutableListOf(FingerprintData("a", 1, 3L))
var nextStep: NextStepViewModel? = null
val job = launch { underTest.nextStep.collect { nextStep = it } }
diff --git a/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsViewModelTest.kt b/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsViewModelTest.kt
index d25ced0..4bd9121 100644
--- a/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsViewModelTest.kt
+++ b/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsViewModelTest.kt
@@ -17,8 +17,8 @@
package com.android.settings.fingerprint2.ui.settings
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsViewModel
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.PreferenceViewModel
@@ -103,7 +103,7 @@
FingerprintSensorType.UDFPS_OPTICAL,
)
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
- mutableListOf(FingerprintViewModel("a", 1, 3L))
+ mutableListOf(FingerprintData("a", 1, 3L))
underTest =
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
@@ -114,7 +114,7 @@
)
.create(FingerprintSettingsViewModel::class.java)
- var authAttempt: FingerprintAuthAttemptViewModel? = null
+ var authAttempt: FingerprintAuthAttemptModel? = null
val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
underTest.shouldAuthenticate(true)
@@ -139,7 +139,7 @@
FingerprintSensorType.UDFPS_ULTRASONIC,
)
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
- mutableListOf(FingerprintViewModel("a", 1, 3L))
+ mutableListOf(FingerprintData("a", 1, 3L))
underTest =
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
@@ -150,7 +150,7 @@
)
.create(FingerprintSettingsViewModel::class.java)
- var authAttempt: FingerprintAuthAttemptViewModel? = null
+ var authAttempt: FingerprintAuthAttemptModel? = null
val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
underTest.shouldAuthenticate(true)
@@ -173,8 +173,8 @@
FingerprintSensorType.POWER_BUTTON
)
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
- mutableListOf(FingerprintViewModel("a", 1, 3L))
- val success = FingerprintAuthAttemptViewModel.Success(1)
+ mutableListOf(FingerprintData("a", 1, 3L))
+ val success = FingerprintAuthAttemptModel.Success(1)
fakeFingerprintManagerInteractor.authenticateAttempt = success
underTest =
@@ -186,7 +186,7 @@
)
.create(FingerprintSettingsViewModel::class.java)
- var authAttempt: FingerprintAuthAttemptViewModel? = null
+ var authAttempt: FingerprintAuthAttemptModel? = null
val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
underTest.shouldAuthenticate(true)
@@ -200,7 +200,7 @@
@Test
fun deleteDialog_showAndDismiss() = runTest {
- val fingerprintToDelete = FingerprintViewModel("A", 1, 10L)
+ val fingerprintToDelete = FingerprintData("A", 1, 10L)
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
mutableListOf(fingerprintToDelete)
@@ -236,7 +236,7 @@
@Test
fun renameDialog_showAndDismiss() = runTest {
- val fingerprintToRename = FingerprintViewModel("World", 1, 10L)
+ val fingerprintToRename = FingerprintData("World", 1, 10L)
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
mutableListOf(fingerprintToRename)
@@ -274,7 +274,7 @@
@Test
fun testTwoDialogsCannotShow_atSameTime() = runTest {
- val fingerprintToDelete = FingerprintViewModel("A", 1, 10L)
+ val fingerprintToDelete = FingerprintData("A", 1, 10L)
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
mutableListOf(fingerprintToDelete)
@@ -311,9 +311,9 @@
fun authenticatePauses_whenPaused() =
testScope.runTest {
val fingerprints = setupAuth()
- val success = FingerprintAuthAttemptViewModel.Success(fingerprints.first().fingerId)
+ val success = FingerprintAuthAttemptModel.Success(fingerprints.first().fingerId)
- var authAttempt: FingerprintAuthAttemptViewModel? = null
+ var authAttempt: FingerprintAuthAttemptModel? = null
val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
@@ -325,7 +325,7 @@
assertThat(authAttempt).isEqualTo(success)
fakeFingerprintManagerInteractor.authenticateAttempt =
- FingerprintAuthAttemptViewModel.Success(10)
+ FingerprintAuthAttemptModel.Success(10)
underTest.shouldAuthenticate(false)
advanceTimeBy(400)
runCurrent()
@@ -340,7 +340,7 @@
testScope.runTest {
val fingerprints = setupAuth()
- var authAttempt: FingerprintAuthAttemptViewModel? = null
+ var authAttempt: FingerprintAuthAttemptModel? = null
val job = launch { underTest.authFlow.take(1).collectLatest { authAttempt = it } }
underTest.shouldAuthenticate(true)
navigationViewModel.onConfirmDevice(true, 10L)
@@ -357,7 +357,7 @@
testScope.runTest {
val fingerprints = setupAuth()
- var authAttempt: FingerprintAuthAttemptViewModel? = null
+ var authAttempt: FingerprintAuthAttemptModel? = null
val job = launch { underTest.authFlow.take(1).collectLatest { authAttempt = it } }
underTest.shouldAuthenticate(true)
navigationViewModel.onConfirmDevice(true, 10L)
@@ -370,7 +370,7 @@
assertThat(authAttempt).isEqualTo(null)
}
- private fun setupAuth(): MutableList<FingerprintViewModel> {
+ private fun setupAuth(): MutableList<FingerprintData> {
fakeFingerprintManagerInteractor.sensorProp =
FingerprintSensor(
0 /* sensorId */,
@@ -379,9 +379,9 @@
FingerprintSensorType.POWER_BUTTON
)
val fingerprints =
- mutableListOf(FingerprintViewModel("a", 1, 3L), FingerprintViewModel("b", 2, 5L))
+ mutableListOf(FingerprintData("a", 1, 3L), FingerprintData("b", 2, 5L))
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = fingerprints
- val success = FingerprintAuthAttemptViewModel.Success(1)
+ val success = FingerprintAuthAttemptModel.Success(1)
fakeFingerprintManagerInteractor.authenticateAttempt = success
underTest =
diff --git a/tests/unit/src/com/android/settings/localepicker/LocaleHelperPreferenceControllerTest.java b/tests/unit/src/com/android/settings/localepicker/LocaleHelperPreferenceControllerTest.java
index 31b8e79..5ac367e 100644
--- a/tests/unit/src/com/android/settings/localepicker/LocaleHelperPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/localepicker/LocaleHelperPreferenceControllerTest.java
@@ -19,12 +19,14 @@
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.verify;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Looper;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settingslib.widget.FooterPreference;
import org.junit.Before;
@@ -37,6 +39,7 @@
public class LocaleHelperPreferenceControllerTest {
private Context mContext;
private LocaleHelperPreferenceController mLocaleHelperPreferenceController;
+ private FakeFeatureFactory mFeatureFactory;
@Mock
private FooterPreference mMockFooterPreference;
@@ -49,11 +52,16 @@
}
mContext = ApplicationProvider.getApplicationContext();
mLocaleHelperPreferenceController = new LocaleHelperPreferenceController(mContext);
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
}
@Test
public void updateFooterPreference_setFooterPreference_hasClickAction() {
mLocaleHelperPreferenceController.updateFooterPreference(mMockFooterPreference);
verify(mMockFooterPreference).setLearnMoreText(anyString());
+ mMockFooterPreference.setLearnMoreAction(v -> {
+ verify(mFeatureFactory.metricsFeatureProvider).action(
+ mContext, SettingsEnums.ACTION_LANGUAGES_LEARN_MORE);
+ });
}
}
diff --git a/tests/unit/src/com/android/settings/regionalpreferences/NumberingSystemItemControllerTest.java b/tests/unit/src/com/android/settings/regionalpreferences/NumberingSystemItemControllerTest.java
index 0a67824..5c42ad9 100644
--- a/tests/unit/src/com/android/settings/regionalpreferences/NumberingSystemItemControllerTest.java
+++ b/tests/unit/src/com/android/settings/regionalpreferences/NumberingSystemItemControllerTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import android.os.LocaleList;
@@ -51,6 +52,7 @@
private NumberingPreferencesFragment mFragment;
private PreferenceScreen mPreferenceScreen;
private LocaleList mCacheLocale;
+ private FakeFeatureFactory mFeatureFactory;
@Before
@UiThreadTest
@@ -59,6 +61,7 @@
Looper.prepare();
}
mApplicationContext = ApplicationProvider.getApplicationContext();
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
mFragment = spy(new NumberingPreferencesFragment());
PreferenceManager preferenceManager = new PreferenceManager(mApplicationContext);
mPreferenceScreen = preferenceManager.createPreferenceScreen(mApplicationContext);
@@ -94,6 +97,10 @@
}
assertTrue(isCallingStartActivity);
+ verify(mFeatureFactory.metricsFeatureProvider).action(
+ mApplicationContext,
+ SettingsEnums.ACTION_CHOOSE_LANGUAGE_FOR_NUMBERS_PREFERENCES,
+ "I_am_the_key");
}
@Test
@@ -114,6 +121,9 @@
mController.handlePreferenceTreeClick(preference);
verify(mFragment).setArguments(any());
+ verify(mFeatureFactory.metricsFeatureProvider).action(
+ mApplicationContext, SettingsEnums.ACTION_SET_NUMBERS_PREFERENCES,
+ "test_key");
}
@Test