diff --git a/aconfig/settings_flag_declarations.aconfig b/aconfig/settings_flag_declarations.aconfig index 2c8eadeff4e..0007bbffb75 100644 --- a/aconfig/settings_flag_declarations.aconfig +++ b/aconfig/settings_flag_declarations.aconfig @@ -49,3 +49,10 @@ flag { description: "Flag to gate support of injected preference icons containing raw data" bug: "351884562" } + +flag { + name: "catalyst_legal_information" + namespace: "android_settings" + description: "This flag controls the About phone > Legal information page migration" + bug: "323791114" +} diff --git a/res/layout/preference_circular_icons.xml b/res/layout/preference_circular_icons.xml index 863d2288513..e1d7cfeb470 100644 --- a/res/layout/preference_circular_icons.xml +++ b/res/layout/preference_circular_icons.xml @@ -58,8 +58,8 @@ android:lineBreakWordStyle="phrase" android:maxLines="10"/> - - + - - diff --git a/res/values/arrays.xml b/res/values/arrays.xml index f8337b3035c..8cbf5dc6ad3 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -1554,7 +1554,7 @@ @*android:drawable/ic_zen_mode_type_theater @*android:drawable/ic_zen_mode_icon_book - @*android:drawable/ic_zen_mode_type_unknown + @*android:drawable/ic_zen_mode_icon_lotus_flower @*android:drawable/ic_zen_mode_type_immersive @*android:drawable/ic_zen_mode_icon_headphones @*android:drawable/ic_zen_mode_icon_tv @@ -1565,10 +1565,10 @@ @*android:drawable/ic_zen_mode_icon_fork_and_knife @*android:drawable/ic_zen_mode_icon_shopping_cart @*android:drawable/ic_zen_mode_icon_child - @*android:drawable/ic_zen_mode_icon_rabbit @*android:drawable/ic_zen_mode_icon_animal_paw - @*android:drawable/ic_zen_mode_type_managed + @*android:drawable/ic_zen_mode_type_unknown + @*android:drawable/ic_zen_mode_type_managed @*android:drawable/ic_zen_mode_type_other @*android:drawable/ic_zen_mode_icon_heart @*android:drawable/ic_zen_mode_icon_house @@ -1616,10 +1616,10 @@ Fork and knife Shopping cart Child - Rabbit Animal paw - Supervisor + Star badge + Two people Star Heart House diff --git a/res/values/strings.xml b/res/values/strings.xml index 4b30dc19abe..939befe4b74 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -9535,6 +9535,23 @@ Minimize interruptions by only allowing important people and apps to reach you + + Set a mode that follows a regular schedule + + Keep your device in sync with your day’s events + + Wake up feeling like 100% + + Put safety first while on the road + + Gain focus to get in the zone + + For moments when courtesy counts + + Guided usage to help you stay in good hands + + Take control of your attention + Warning diff --git a/res/xml/power_usage_advanced.xml b/res/xml/power_usage_advanced.xml index 816e1974cb0..f2c3d878e19 100644 --- a/res/xml/power_usage_advanced.xml +++ b/res/xml/power_usage_advanced.xml @@ -57,7 +57,7 @@ "com.android.settings.fuelgauge.batteryusage.BatteryUsageBreakdownController" settings:isPreferenceVisible="false"> - diff --git a/src/com/android/settings/SettingsApplication.java b/src/com/android/settings/SettingsApplication.java index 5b052f22dc7..d208fdf09e1 100644 --- a/src/com/android/settings/SettingsApplication.java +++ b/src/com/android/settings/SettingsApplication.java @@ -18,7 +18,9 @@ package com.android.settings; import android.app.Application; import android.content.Context; +import android.content.pm.PackageManager; import android.database.ContentObserver; +import android.hardware.fingerprint.FingerprintManager; import android.net.Uri; import android.provider.Settings; import android.util.FeatureFlagUtils; @@ -74,9 +76,6 @@ public class SettingsApplication extends Application { // Set Spa environment. setSpaEnvironment(); - if (Flags.fingerprintV2Enrollment()) { - mBiometricsEnvironment = new BiometricsEnvironment(this); - } if (ActivityEmbeddingUtils.isSettingsSplitEnabled(this) && FeatureFlagUtils.isEnabled(this, @@ -120,7 +119,20 @@ public class SettingsApplication extends Application { @Nullable public BiometricsEnvironment getBiometricEnvironment() { - return mBiometricsEnvironment; + if (Flags.fingerprintV2Enrollment()) { + if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { + final FingerprintManager fpm = getSystemService(FingerprintManager.class); + if (mBiometricsEnvironment == null) { + mBiometricsEnvironment = new BiometricsEnvironment(this, fpm); + } + return mBiometricsEnvironment; + + } else { + return null; + } + + } + return null; } @Override diff --git a/src/com/android/settings/accessibility/DaltonizerPreferenceUtil.java b/src/com/android/settings/accessibility/DaltonizerPreferenceUtil.java new file mode 100644 index 00000000000..459dbb98451 --- /dev/null +++ b/src/com/android/settings/accessibility/DaltonizerPreferenceUtil.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 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.accessibility; + +import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; +import static com.android.settings.accessibility.AccessibilityUtil.State.ON; + +import android.content.ContentResolver; +import android.provider.Settings; +import android.view.accessibility.AccessibilityManager; + +import com.google.common.primitives.Ints; + +/** + * Utility class for retrieving accessibility daltonizer related values in secure settings. + */ +public class DaltonizerPreferenceUtil { + + /** + * Return the daltonizer display mode stored in + * {@link Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER}. + * By default it returns {@link DALTONIZER_CORRECT_DEUTERANOMALY}. + */ + public static int getSecureAccessibilityDaltonizerValue(ContentResolver resolver) { + final String daltonizerStringValue = Settings.Secure.getString( + resolver, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER); + if (daltonizerStringValue == null) { + return AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY; + } + final Integer daltonizerIntValue = Ints.tryParse(daltonizerStringValue); + return daltonizerIntValue == null ? AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY + : daltonizerIntValue; + } + + /** + * Returns the daltonizer enabled value in + * {@link Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED}. + * By default it returns false. + */ + public static boolean isSecureAccessibilityDaltonizerEnabled(ContentResolver resolver) { + return Settings.Secure.getInt( + resolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, + OFF) == ON; + } +} diff --git a/src/com/android/settings/accessibility/DaltonizerRadioButtonPreferenceController.java b/src/com/android/settings/accessibility/DaltonizerRadioButtonPreferenceController.java index 296536c2faf..5a8c710b55c 100644 --- a/src/com/android/settings/accessibility/DaltonizerRadioButtonPreferenceController.java +++ b/src/com/android/settings/accessibility/DaltonizerRadioButtonPreferenceController.java @@ -24,7 +24,6 @@ import android.os.Handler; import android.os.Looper; import android.provider.Settings; import android.view.View; -import android.view.accessibility.AccessibilityManager; import androidx.annotation.NonNull; import androidx.lifecycle.DefaultLifecycleObserver; @@ -36,8 +35,6 @@ import com.android.settings.R; import com.android.settings.core.BasePreferenceController; import com.android.settingslib.widget.SelectorWithWidgetPreference; -import com.google.common.primitives.Ints; - import java.util.HashMap; import java.util.Map; @@ -70,17 +67,6 @@ public class DaltonizerRadioButtonPreferenceController extends BasePreferenceCon }; } - protected static int getSecureAccessibilityDaltonizerValue(ContentResolver resolver) { - final String daltonizerStringValue = Settings.Secure.getString( - resolver, DALTONIZER_TYPE_SETTINGS_KEY); - if (daltonizerStringValue == null) { - return AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY; - } - final Integer daltonizerIntValue = Ints.tryParse(daltonizerStringValue); - return daltonizerIntValue == null ? AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY - : daltonizerIntValue; - } - private Map getDaltonizerValueToKeyMap() { if (mAccessibilityDaltonizerKeyToValueMap.isEmpty()) { @@ -123,7 +109,8 @@ public class DaltonizerRadioButtonPreferenceController extends BasePreferenceCon } private int getAccessibilityDaltonizerValue() { - final int daltonizerValue = getSecureAccessibilityDaltonizerValue(mContentResolver); + final int daltonizerValue = + DaltonizerPreferenceUtil.getSecureAccessibilityDaltonizerValue(mContentResolver); return daltonizerValue; } diff --git a/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java b/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java index 29971854e6b..6a9977ad2d7 100644 --- a/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java +++ b/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java @@ -15,6 +15,9 @@ */ package com.android.settings.accessibility; +import static com.android.settings.accessibility.DaltonizerPreferenceUtil.isSecureAccessibilityDaltonizerEnabled; +import static com.android.settings.accessibility.DaltonizerPreferenceUtil.getSecureAccessibilityDaltonizerValue; + import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; @@ -158,14 +161,11 @@ public class DaltonizerSaturationSeekbarPreferenceController } private boolean shouldSeekBarEnabled() { - int enabled = Settings.Secure.getInt( - mContentResolver, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0); - int mode = Settings.Secure.getInt( - mContentResolver, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, -1); + boolean enabled = isSecureAccessibilityDaltonizerEnabled(mContentResolver); + int mode = getSecureAccessibilityDaltonizerValue(mContentResolver); - // enabled == 0 is disabled and also default. // mode == 0 is gray scale where saturation level isn't applicable. // mode == -1 is disabled and also default. - return enabled != 0 && mode != -1 && mode != 0; + return enabled && mode != -1 && mode != 0; } } diff --git a/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java index 52f1695cbe5..86ddd71e262 100644 --- a/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java @@ -21,6 +21,7 @@ import static com.android.internal.accessibility.AccessibilityShortcutController import static com.android.settings.accessibility.AccessibilityStatsLogUtils.logAccessibilityServiceEnabled; import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; import static com.android.settings.accessibility.AccessibilityUtil.State.ON; +import static com.android.settings.accessibility.DaltonizerPreferenceUtil.isSecureAccessibilityDaltonizerEnabled; import android.app.settings.SettingsEnums; import android.content.ComponentName; @@ -145,7 +146,8 @@ public class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePreferenceF @Override protected void onPreferenceToggled(String preferenceKey, boolean enabled) { - final boolean isEnabled = Settings.Secure.getInt(getContentResolver(), ENABLED, OFF) == ON; + final boolean isEnabled = + isSecureAccessibilityDaltonizerEnabled(getContentResolver()); if (enabled == isEnabled) { return; } diff --git a/src/com/android/settings/biometrics/fingerprint2/BiometricsEnvironment.kt b/src/com/android/settings/biometrics/fingerprint2/BiometricsEnvironment.kt index 9bc920a96ba..e3233ed22b1 100644 --- a/src/com/android/settings/biometrics/fingerprint2/BiometricsEnvironment.kt +++ b/src/com/android/settings/biometrics/fingerprint2/BiometricsEnvironment.kt @@ -16,12 +16,9 @@ package com.android.settings.biometrics.fingerprint2 -import android.content.pm.PackageManager import android.hardware.fingerprint.FingerprintManager -import android.os.ServiceManager.ServiceNotFoundException import android.view.MotionEvent import android.view.accessibility.AccessibilityManager -import androidx.fragment.app.FragmentActivity import androidx.lifecycle.ViewModelStore import androidx.lifecycle.ViewModelStoreOwner import com.android.internal.widget.LockPatternUtils @@ -29,33 +26,47 @@ import com.android.settings.SettingsApplication import com.android.settings.biometrics.GatekeeperPasswordProvider import com.android.settings.biometrics.fingerprint2.data.repository.DebuggingRepository import com.android.settings.biometrics.fingerprint2.data.repository.DebuggingRepositoryImpl +import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintEnrollmentRepositoryImpl import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepositoryImpl +import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSettingsRepositoryImpl +import com.android.settings.biometrics.fingerprint2.data.repository.UserRepoImpl import com.android.settings.biometrics.fingerprint2.debug.data.repository.UdfpsEnrollDebugRepositoryImpl import com.android.settings.biometrics.fingerprint2.debug.domain.interactor.DebugTouchEventInteractorImpl import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractorImpl +import com.android.settings.biometrics.fingerprint2.domain.interactor.AuthenticateInteractorImpl +import com.android.settings.biometrics.fingerprint2.domain.interactor.CanEnrollFingerprintsInteractorImpl import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractorImpl import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractorImpl +import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollFingerprintInteractorImpl import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractorImpl -import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractor -import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl -import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl +import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrolledFingerprintsInteractorImpl import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintSensorInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintSensorInteractorImpl import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractorImpl +import com.android.settings.biometrics.fingerprint2.domain.interactor.GenerateChallengeInteractorImpl import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractorImpl +import com.android.settings.biometrics.fingerprint2.domain.interactor.RemoveFingerprintsInteractorImpl +import com.android.settings.biometrics.fingerprint2.domain.interactor.RenameFingerprintsInteractorImpl +import com.android.settings.biometrics.fingerprint2.domain.interactor.SensorInteractorImpl import com.android.settings.biometrics.fingerprint2.domain.interactor.TouchEventInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractorImpl import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractorImpl -import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.AuthenitcateInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrollFingerprintInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RemoveFingerprintInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RenameFingerprintInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor import com.android.settings.biometrics.fingerprint2.lib.model.Settings import java.util.concurrent.Executors import kotlinx.coroutines.MainScope @@ -70,43 +81,53 @@ import kotlinx.coroutines.flow.flowOf * This code is instantiated within the [SettingsApplication], all repos should be private & * immutable and all interactors should public and immutable */ -class BiometricsEnvironment(context: SettingsApplication) : ViewModelStoreOwner { - +class BiometricsEnvironment( + val context: SettingsApplication, + private val fingerprintManager: FingerprintManager, +) : ViewModelStoreOwner { private val executorService = Executors.newSingleThreadExecutor() private val backgroundDispatcher = executorService.asCoroutineDispatcher() private val applicationScope = MainScope() private val gateKeeperPasswordProvider = GatekeeperPasswordProvider(LockPatternUtils(context)) - private val fingerprintManager = try { - if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { - context.getSystemService(FragmentActivity.FINGERPRINT_SERVICE) as FingerprintManager? - } else { - null - } - } catch (exception: ServiceNotFoundException){ - null - } + private val userRepo = UserRepoImpl(context.userId) + private val fingerprintSettingsRepository = + FingerprintSettingsRepositoryImpl( + context.resources.getInteger( + com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser + ) + ) + private val fingerprintEnrollmentRepository = + FingerprintEnrollmentRepositoryImpl(fingerprintManager, userRepo, fingerprintSettingsRepository, + backgroundDispatcher, applicationScope) private val fingerprintSensorRepository: FingerprintSensorRepository = FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, applicationScope) private val debuggingRepository: DebuggingRepository = DebuggingRepositoryImpl() private val udfpsDebugRepo = UdfpsEnrollDebugRepositoryImpl() - /** For now, interactors are public to those with access to the [BiometricsEnvironment] class */ - val fingerprintEnrollInteractor: FingerprintEnrollInteractor by lazy { - FingerprintEnrollInteractorImpl(context, fingerprintManager, Settings) - } + fun createSensorPropertiesInteractor(): SensorInteractor = + SensorInteractorImpl(fingerprintSensorRepository) - /** [FingerprintManagerInteractor] to be used to construct view models */ - val fingerprintManagerInteractor: FingerprintManagerInteractor by lazy { - FingerprintManagerInteractorImpl( - context, - backgroundDispatcher, - fingerprintManager, - fingerprintSensorRepository, - gateKeeperPasswordProvider, - fingerprintEnrollInteractor, - ) - } + fun createCanEnrollFingerprintsInteractor(): CanEnrollFingerprintsInteractor = + CanEnrollFingerprintsInteractorImpl(fingerprintEnrollmentRepository) + + fun createGenerateChallengeInteractor(): GenerateChallengeInteractor = + GenerateChallengeInteractorImpl(fingerprintManager, context.userId, gateKeeperPasswordProvider) + + fun createFingerprintEnrollInteractor(): EnrollFingerprintInteractor = + EnrollFingerprintInteractorImpl(context.userId, fingerprintManager, Settings) + + fun createFingerprintsEnrolledInteractor(): EnrolledFingerprintsInteractorImpl = + EnrolledFingerprintsInteractorImpl(fingerprintManager, context.userId) + + fun createAuthenticateInteractor(): AuthenitcateInteractor = + AuthenticateInteractorImpl(fingerprintManager, context.userId) + + fun createRemoveFingerprintInteractor(): RemoveFingerprintInteractor = + RemoveFingerprintsInteractorImpl(fingerprintManager, context.userId) + + fun createRenameFingerprintInteractor(): RenameFingerprintInteractor = + RenameFingerprintsInteractorImpl(fingerprintManager, context.userId, backgroundDispatcher) val accessibilityInteractor: AccessibilityInteractor by lazy { AccessibilityInteractorImpl( diff --git a/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintEnrollmentRepo.kt b/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintEnrollmentRepo.kt new file mode 100644 index 00000000000..22904e9d2ac --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintEnrollmentRepo.kt @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2024 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.data.repository + +import android.hardware.biometrics.BiometricStateListener +import android.hardware.fingerprint.FingerprintManager +import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext + +/** Repository that contains information about fingerprint enrollments. */ +interface FingerprintEnrollmentRepository { + /** The current enrollments of the user */ + val currentEnrollments: Flow?> + + /** Indicates if a user can enroll another fingerprint */ + val canEnrollUser: Flow + + fun maxFingerprintsEnrollable(): Int +} + +class FingerprintEnrollmentRepositoryImpl( + fingerprintManager: FingerprintManager, + userRepo: UserRepo, + private val settingsRepository: FingerprintSettingsRepository, + backgroundDispatcher: CoroutineDispatcher, + applicationScope: CoroutineScope, +) : FingerprintEnrollmentRepository { + + private val enrollmentChangedFlow: Flow = + callbackFlow { + val callback = + object : BiometricStateListener() { + override fun onEnrollmentsChanged(userId: Int, sensorId: Int, hasEnrollments: Boolean) { + trySend(userId) + } + } + withContext(backgroundDispatcher) { + fingerprintManager.registerBiometricStateListener(callback) + } + awaitClose { + // no way to unregister + } + } + .stateIn(applicationScope, started = SharingStarted.Eagerly, initialValue = null) + + override val currentEnrollments: Flow> = + userRepo.currentUser + .distinctUntilChanged() + .flatMapLatest { currentUser -> + enrollmentChangedFlow.map { enrollmentChanged -> + if (enrollmentChanged == null || enrollmentChanged == currentUser) { + fingerprintManager + .getEnrolledFingerprints(currentUser) + ?.map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) } + ?.toList() + } else { + null + } + } + } + .filterNotNull() + .flowOn(backgroundDispatcher) + + override val canEnrollUser: Flow = + currentEnrollments.map { + it?.size?.let { it < settingsRepository.maxEnrollableFingerprints() } ?: false + } + + override fun maxFingerprintsEnrollable(): Int { + return settingsRepository.maxEnrollableFingerprints() + } +} diff --git a/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintSensorRepository.kt b/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintSensorRepository.kt index 516549e65f4..1cca532c6ac 100644 --- a/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintSensorRepository.kt +++ b/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintSensorRepository.kt @@ -31,6 +31,8 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transform import kotlinx.coroutines.withContext @@ -43,10 +45,13 @@ import kotlinx.coroutines.withContext interface FingerprintSensorRepository { /** Get the [FingerprintSensor] */ val fingerprintSensor: Flow + + /** Indicates if this device supports the side fingerprint sensor */ + val hasSideFps: Flow } class FingerprintSensorRepositoryImpl( - fingerprintManager: FingerprintManager?, + private val fingerprintManager: FingerprintManager, backgroundDispatcher: CoroutineDispatcher, activityScope: CoroutineScope, ) : FingerprintSensorRepository { @@ -66,7 +71,7 @@ class FingerprintSensorRepositoryImpl( } } withContext(backgroundDispatcher) { - fingerprintManager?.addAuthenticatorsRegisteredCallback(callback) + fingerprintManager.addAuthenticatorsRegisteredCallback(callback) } awaitClose {} } @@ -75,6 +80,9 @@ class FingerprintSensorRepositoryImpl( override val fingerprintSensor: Flow = fingerprintPropsInternal.transform { emit(it.toFingerprintSensor()) } + override val hasSideFps: Flow = + fingerprintSensor.flatMapLatest { flow { emit(fingerprintManager.isPowerbuttonFps()) } } + companion object { private val DEFAULT_PROPS = diff --git a/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintSettingsRepository.kt b/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintSettingsRepository.kt new file mode 100644 index 00000000000..fe6676c1608 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintSettingsRepository.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 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.data.repository + +/** + * Repository for storing metadata about fingerprint enrollments. + */ +interface FingerprintSettingsRepository { + /** + * Indicates the maximum number of fingerprints enrollable + */ + fun maxEnrollableFingerprints(): Int +} + +class FingerprintSettingsRepositoryImpl(private val maxFingerprintsEnrollable: Int) : + FingerprintSettingsRepository { + override fun maxEnrollableFingerprints() = maxFingerprintsEnrollable +} diff --git a/src/com/android/settings/biometrics/fingerprint2/data/repository/SimulatedTouchEventsRepository.kt b/src/com/android/settings/biometrics/fingerprint2/data/repository/SimulatedTouchEventsRepository.kt index 3c355e76853..9b7f2808ee7 100644 --- a/src/com/android/settings/biometrics/fingerprint2/data/repository/SimulatedTouchEventsRepository.kt +++ b/src/com/android/settings/biometrics/fingerprint2/data/repository/SimulatedTouchEventsRepository.kt @@ -16,7 +16,6 @@ package com.android.settings.biometrics.fingerprint2.data.repository -import android.graphics.Point import android.view.MotionEvent import kotlinx.coroutines.flow.Flow diff --git a/src/com/android/settings/biometrics/fingerprint2/data/repository/UserRepo.kt b/src/com/android/settings/biometrics/fingerprint2/data/repository/UserRepo.kt new file mode 100644 index 00000000000..720e7787d12 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/data/repository/UserRepo.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 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.data.repository + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +/** + * A repository responsible for indicating the current user. + */ +interface UserRepo { + /** + * This flow indicates the current user. + */ + val currentUser: Flow +} + +class UserRepoImpl(val currUser: Int): UserRepo { + override val currentUser: Flow = flowOf(currUser) +} diff --git a/src/com/android/settings/biometrics/fingerprint2/debug/data/repository/UdfpsEnrollDebugRepository.kt b/src/com/android/settings/biometrics/fingerprint2/debug/data/repository/UdfpsEnrollDebugRepository.kt index 0c3152a83ae..bc48f074329 100644 --- a/src/com/android/settings/biometrics/fingerprint2/debug/data/repository/UdfpsEnrollDebugRepository.kt +++ b/src/com/android/settings/biometrics/fingerprint2/debug/data/repository/UdfpsEnrollDebugRepository.kt @@ -97,6 +97,8 @@ class UdfpsEnrollDebugRepositoryImpl : } override val fingerprintSensor: Flow = flowOf(sensorProps) + override val hasSideFps: Flow + get() = flowOf(false) private fun pointToLeftOfSensor(sensorLocation: Rect): MotionEvent = MotionEvent.obtain( diff --git a/src/com/android/settings/biometrics/fingerprint2/debug/domain/interactor/DebugTouchEventInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/debug/domain/interactor/DebugTouchEventInteractorImpl.kt index fff6b6609fd..f6627e1d8e9 100644 --- a/src/com/android/settings/biometrics/fingerprint2/debug/domain/interactor/DebugTouchEventInteractorImpl.kt +++ b/src/com/android/settings/biometrics/fingerprint2/debug/domain/interactor/DebugTouchEventInteractorImpl.kt @@ -26,4 +26,4 @@ class DebugTouchEventInteractorImpl( ) : TouchEventInteractor { override val touchEvent: Flow = udfpsSimulatedTouchEventsRepository.touchExplorationDebug -} \ No newline at end of file +} diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/AuthenticateInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/AuthenticateInteractorImpl.kt new file mode 100644 index 00000000000..df93092619f --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/AuthenticateInteractorImpl.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2024 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.domain.interactor + +import android.hardware.fingerprint.FingerprintManager +import android.os.CancellationSignal +import android.util.Log +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.AuthenitcateInteractor +import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel +import kotlin.coroutines.resume +import kotlinx.coroutines.CancellableContinuation +import kotlinx.coroutines.suspendCancellableCoroutine + +class AuthenticateInteractorImpl( + private val fingerprintManager: FingerprintManager, + private val userId: Int, +) : AuthenitcateInteractor { + + override suspend fun authenticate(): FingerprintAuthAttemptModel = + suspendCancellableCoroutine { c: CancellableContinuation -> + val authenticationCallback = + object : FingerprintManager.AuthenticationCallback() { + + override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { + super.onAuthenticationError(errorCode, errString) + if (c.isCompleted) { + Log.d(TAG, "framework sent down onAuthError after finish") + return + } + c.resume(FingerprintAuthAttemptModel.Error(errorCode, errString.toString())) + } + + override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) { + super.onAuthenticationSucceeded(result) + if (c.isCompleted) { + Log.d(TAG, "framework sent down onAuthError after finish") + return + } + c.resume(FingerprintAuthAttemptModel.Success(result.fingerprint?.biometricId ?: -1)) + } + } + + val cancellationSignal = CancellationSignal() + c.invokeOnCancellation { cancellationSignal.cancel() } + fingerprintManager.authenticate( + null, + cancellationSignal, + authenticationCallback, + null, + userId, + ) + } + + companion object { + private const val TAG = "AuthenticateInteractor" + } +} diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/CanEnrollFingerprintsInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/CanEnrollFingerprintsInteractorImpl.kt new file mode 100644 index 00000000000..caeea4e4586 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/CanEnrollFingerprintsInteractorImpl.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 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.domain.interactor + +import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintEnrollmentRepository +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor +import kotlinx.coroutines.flow.Flow + +class CanEnrollFingerprintsInteractorImpl( + val fingerprintEnrollmentRepository: FingerprintEnrollmentRepository +) : CanEnrollFingerprintsInteractor { + override val canEnrollFingerprints: Flow = fingerprintEnrollmentRepository.canEnrollUser + /** Indicates the maximum fingerprints enrollable for a given user */ + override fun maxFingerprintsEnrollable(): Int { + return fingerprintEnrollmentRepository.maxFingerprintsEnrollable() + } +} diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/EnrollFingerprintInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/EnrollFingerprintInteractorImpl.kt new file mode 100644 index 00000000000..3e14a640278 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/EnrollFingerprintInteractorImpl.kt @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2024 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.domain.interactor + +import android.hardware.fingerprint.FingerprintEnrollOptions +import android.hardware.fingerprint.FingerprintManager +import android.os.CancellationSignal +import android.util.Log +import com.android.settings.biometrics.fingerprint2.conversion.Util.toEnrollError +import com.android.settings.biometrics.fingerprint2.conversion.Util.toOriginalReason +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrollFingerprintInteractor +import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason +import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState +import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow +import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard +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.update + +class EnrollFingerprintInteractorImpl( + private val userId: Int, + private val fingerprintManager: FingerprintManager, + private val fingerprintFlow: FingerprintFlow, +) : EnrollFingerprintInteractor { + private val enrollRequestOutstanding = MutableStateFlow(false) + + override suspend fun enroll( + hardwareAuthToken: ByteArray?, + enrollReason: EnrollReason, + fingerprintEnrollOptions: FingerprintEnrollOptions, + ): Flow = 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) { + // 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(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(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 } + } + + override fun onUdfpsPointerDown(sensorId: Int) { + trySend(FingerEnrollState.PointerDown(sensorId)).onFailure { error -> + Log.d(TAG, "onUdfpsPointerDown failed to send, due to $error") + } + } + + override fun onUdfpsPointerUp(sensorId: Int) { + trySend(FingerEnrollState.PointerUp(sensorId)).onFailure { error -> + Log.d(TAG, "onUdfpsPointerUp failed to send, due to $error") + } + } + + override fun onUdfpsOverlayShown() { + trySend(FingerEnrollState.OverlayShown).onFailure { error -> + Log.d(TAG, "OverlayShown failed to send, due to $error") + } + } + + override fun onAcquired(isAcquiredGood: Boolean) { + trySend(FingerEnrollState.Acquired(isAcquiredGood)).onFailure { error -> + Log.d(TAG, "Acquired failed to send, due to $error") + } + } + } + + val cancellationSignal = CancellationSignal() + + fingerprintManager.enroll( + hardwareAuthToken, + cancellationSignal, + userId, + enrollmentCallback, + enrollReason.toOriginalReason(), + fingerprintEnrollOptions, + ) + awaitClose { + // 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() + } + } + } + + companion object { + private const val TAG = "FingerprintEnrollStateRepository" + } +} diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/EnrolledFingerprintsInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/EnrolledFingerprintsInteractorImpl.kt new file mode 100644 index 00000000000..83b532ecd98 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/EnrolledFingerprintsInteractorImpl.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 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.domain.interactor + +import android.hardware.fingerprint.FingerprintManager +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrolledFingerprintsInteractor +import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow + +class EnrolledFingerprintsInteractorImpl( + private val fingerprintManager: FingerprintManager, + userId: Int, +) : EnrolledFingerprintsInteractor { + override val enrolledFingerprints: Flow?> = flow { + emit( + fingerprintManager + .getEnrolledFingerprints(userId) + ?.map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) } + ?.toList() + ) + } +} diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintEnrollInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintEnrollInteractor.kt index a36832db076..56a125794b7 100644 --- a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintEnrollInteractor.kt +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintEnrollInteractor.kt @@ -16,7 +16,6 @@ package com.android.settings.biometrics.fingerprint2.domain.interactor -import android.content.Context import android.hardware.fingerprint.FingerprintEnrollOptions import android.hardware.fingerprint.FingerprintManager import android.os.CancellationSignal @@ -49,7 +48,7 @@ interface FingerprintEnrollInteractor { } class FingerprintEnrollInteractorImpl( - private val applicationContext: Context, + private val userId: Int, private val fingerprintManager: FingerprintManager?, private val fingerprintFlow: FingerprintFlow, ) : FingerprintEnrollInteractor { @@ -138,7 +137,7 @@ class FingerprintEnrollInteractorImpl( fingerprintManager?.enroll( hardwareAuthToken, cancellationSignal, - applicationContext.userId, + userId, enrollmentCallback, enrollReason.toOriginalReason(), fingerprintEnrollOptions, diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt deleted file mode 100644 index f03c94ea047..00000000000 --- a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt +++ /dev/null @@ -1,173 +0,0 @@ -/* - * 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.domain.interactor - -import android.content.Context -import android.content.Intent -import android.hardware.fingerprint.FingerprintEnrollOptions -import android.hardware.fingerprint.FingerprintManager -import android.hardware.fingerprint.FingerprintManager.GenerateChallengeCallback -import android.hardware.fingerprint.FingerprintManager.RemovalCallback -import android.os.CancellationSignal -import android.util.Log -import com.android.settings.biometrics.GatekeeperPasswordProvider -import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository -import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor -import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason -import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState -import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel -import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData -import com.android.settings.password.ChooseLockSettingsHelper -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine -import kotlinx.coroutines.CancellableContinuation -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withContext - -private const val TAG = "FingerprintManagerInteractor" - -class FingerprintManagerInteractorImpl( - applicationContext: Context, - private val backgroundDispatcher: CoroutineDispatcher, - private val fingerprintManager: FingerprintManager?, - fingerprintSensorRepository: FingerprintSensorRepository, - private val gatekeeperPasswordProvider: GatekeeperPasswordProvider, - private val fingerprintEnrollStateRepository: FingerprintEnrollInteractor, -) : FingerprintManagerInteractor { - - private val maxFingerprints = - applicationContext.resources.getInteger( - com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser - ) - private val applicationContext = applicationContext.applicationContext - - override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair = - suspendCoroutine { - val callback = GenerateChallengeCallback { _, userId, challenge -> - val intent = Intent() - intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gateKeeperPasswordHandle) - val challengeToken = - gatekeeperPasswordProvider.requestGatekeeperHat(intent, challenge, userId) - - gatekeeperPasswordProvider.removeGatekeeperPasswordHandle(intent, false) - val p = Pair(challenge, challengeToken) - it.resume(p) - } - fingerprintManager?.generateChallenge(applicationContext.userId, callback) - } - - override val enrolledFingerprints: Flow?> = flow { - emit( - fingerprintManager?.getEnrolledFingerprints(applicationContext.userId) - ?.map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) }?.toList() - ) - } - - override val canEnrollFingerprints: Flow = flow { - emit( - fingerprintManager?.getEnrolledFingerprints(applicationContext.userId)?.size ?: maxFingerprints < maxFingerprints - ) - } - - override val sensorPropertiesInternal = fingerprintSensorRepository.fingerprintSensor - - override val maxEnrollableFingerprints = flow { emit(maxFingerprints) } - - override suspend fun enroll( - hardwareAuthToken: ByteArray?, - enrollReason: EnrollReason, - fingerprintEnrollOptions: FingerprintEnrollOptions, - ): Flow = - fingerprintEnrollStateRepository.enroll( - hardwareAuthToken, - enrollReason, - fingerprintEnrollOptions, - ) - - override suspend fun removeFingerprint(fp: FingerprintData): Boolean = suspendCoroutine { - val callback = - object : RemovalCallback() { - override fun onRemovalError( - fp: android.hardware.fingerprint.Fingerprint, - errMsgId: Int, - errString: CharSequence, - ) { - it.resume(false) - } - - override fun onRemovalSucceeded( - fp: android.hardware.fingerprint.Fingerprint?, - remaining: Int, - ) { - it.resume(true) - } - } - fingerprintManager?.remove( - android.hardware.fingerprint.Fingerprint(fp.name, fp.fingerId, fp.deviceId), - applicationContext.userId, - callback, - ) - } - - override suspend fun renameFingerprint(fp: FingerprintData, newName: String) { - withContext(backgroundDispatcher) { - fingerprintManager?.rename(fp.fingerId, applicationContext.userId, newName) - } - } - - override suspend fun hasSideFps(): Boolean? = suspendCancellableCoroutine { - it.resume(fingerprintManager?.isPowerbuttonFps) - } - - override suspend fun authenticate(): FingerprintAuthAttemptModel = - suspendCancellableCoroutine { c: CancellableContinuation -> - val authenticationCallback = - object : FingerprintManager.AuthenticationCallback() { - - override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { - super.onAuthenticationError(errorCode, errString) - if (c.isCompleted) { - Log.d(TAG, "framework sent down onAuthError after finish") - return - } - c.resume(FingerprintAuthAttemptModel.Error(errorCode, errString.toString())) - } - - override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) { - super.onAuthenticationSucceeded(result) - if (c.isCompleted) { - Log.d(TAG, "framework sent down onAuthError after finish") - return - } - c.resume(FingerprintAuthAttemptModel.Success(result.fingerprint?.biometricId ?: -1)) - } - } - - val cancellationSignal = CancellationSignal() - c.invokeOnCancellation { cancellationSignal.cancel() } - fingerprintManager?.authenticate( - null, - cancellationSignal, - authenticationCallback, - null, - applicationContext.userId, - ) - } -} diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintSensorInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintSensorInteractor.kt index 073629c6537..7b1d4fd280c 100644 --- a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintSensorInteractor.kt +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintSensorInteractor.kt @@ -20,9 +20,7 @@ import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintS import com.android.systemui.biometrics.shared.model.FingerprintSensor import kotlinx.coroutines.flow.Flow -/** - * Interactor that propagates the type of [FingerprintSensor] this device supports. - */ +/** Interactor that propagates the type of [FingerprintSensor] this device supports. */ interface FingerprintSensorInteractor { /** Get the [FingerprintSensor] */ val fingerprintSensor: Flow diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/GenerateChallengeInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/GenerateChallengeInteractorImpl.kt new file mode 100644 index 00000000000..a2080fbb74f --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/GenerateChallengeInteractorImpl.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 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.domain.interactor + +import android.content.Intent +import android.hardware.fingerprint.FingerprintManager +import com.android.settings.biometrics.GatekeeperPasswordProvider +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor +import com.android.settings.password.ChooseLockSettingsHelper +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +class GenerateChallengeInteractorImpl( + private val fingerprintManager: FingerprintManager, + private val userId: Int, + private val gatekeeperPasswordProvider: GatekeeperPasswordProvider, +) : GenerateChallengeInteractor { + + override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair = + suspendCoroutine { + val callback = + FingerprintManager.GenerateChallengeCallback { _, userId, challenge -> + val intent = Intent() + intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gateKeeperPasswordHandle) + val challengeToken = + gatekeeperPasswordProvider.requestGatekeeperHat(intent, challenge, userId) + + gatekeeperPasswordProvider.removeGatekeeperPasswordHandle(intent, false) + val p = Pair(challenge, challengeToken) + it.resume(p) + } + fingerprintManager.generateChallenge(userId, callback) + } +} diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/RemoveFingerprintsInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/RemoveFingerprintsInteractorImpl.kt new file mode 100644 index 00000000000..4232963ad55 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/RemoveFingerprintsInteractorImpl.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 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.domain.interactor + +import android.hardware.fingerprint.FingerprintManager +import android.hardware.fingerprint.FingerprintManager.RemovalCallback +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RemoveFingerprintInteractor +import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +class RemoveFingerprintsInteractorImpl( + private val fingerprintManager: FingerprintManager, + private val userId: Int, +) : RemoveFingerprintInteractor { + + override suspend fun removeFingerprint(fp: FingerprintData): Boolean = suspendCoroutine { + val callback = + object : RemovalCallback() { + override fun onRemovalError( + fp: android.hardware.fingerprint.Fingerprint, + errMsgId: Int, + errString: CharSequence, + ) { + it.resume(false) + } + + override fun onRemovalSucceeded( + fp: android.hardware.fingerprint.Fingerprint?, + remaining: Int, + ) { + it.resume(true) + } + } + fingerprintManager.remove( + android.hardware.fingerprint.Fingerprint(fp.name, fp.fingerId, fp.deviceId), + userId, + callback, + ) + } +} diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/RenameFingerprintsInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/RenameFingerprintsInteractorImpl.kt new file mode 100644 index 00000000000..f238e7c4f2e --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/RenameFingerprintsInteractorImpl.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 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.domain.interactor + +import android.hardware.fingerprint.FingerprintManager +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RenameFingerprintInteractor +import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext + +class RenameFingerprintsInteractorImpl( + private val fingerprintManager: FingerprintManager, + private val userId: Int, + private val backgroundDispatcher: CoroutineDispatcher, +) : RenameFingerprintInteractor { + + override suspend fun renameFingerprint(fp: FingerprintData, newName: String) { + withContext(backgroundDispatcher) { fingerprintManager.rename(fp.fingerId, userId, newName) } + } +} diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/SensorInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/SensorInteractorImpl.kt new file mode 100644 index 00000000000..7df0795e4ec --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/SensorInteractorImpl.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 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.domain.interactor + +import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor +import kotlinx.coroutines.flow.Flow + +class SensorInteractorImpl(private val repo: FingerprintSensorRepository) : + SensorInteractor { + override val sensorPropertiesInternal = repo.fingerprintSensor + override val hasSideFps: Flow = repo.hasSideFps +} diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/TouchEventInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/TouchEventInteractor.kt index 4ef2afa0875..778837d84ea 100644 --- a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/TouchEventInteractor.kt +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/TouchEventInteractor.kt @@ -24,4 +24,3 @@ interface TouchEventInteractor { /** A flow simulating user touches. */ val touchEvent: Flow } - diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/AndroidManifest.xml b/src/com/android/settings/biometrics/fingerprint2/lib/AndroidManifest.xml index 250f0af83eb..0b7ea2841ef 100644 --- a/src/com/android/settings/biometrics/fingerprint2/lib/AndroidManifest.xml +++ b/src/com/android/settings/biometrics/fingerprint2/lib/AndroidManifest.xml @@ -13,6 +13,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/AuthenitcateInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/AuthenitcateInteractor.kt new file mode 100644 index 00000000000..4fc94137bc0 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/AuthenitcateInteractor.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 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.lib.domain.interactor + +import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel + +/** Interactor responsible for coordinating authentication. */ +interface AuthenitcateInteractor { + /** Runs the authenticate flow */ + suspend fun authenticate(): FingerprintAuthAttemptModel +} diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/CanEnrollFingerprintsInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/CanEnrollFingerprintsInteractor.kt new file mode 100644 index 00000000000..11a9258ed88 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/CanEnrollFingerprintsInteractor.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 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.lib.domain.interactor + +import kotlinx.coroutines.flow.Flow + +/** Returns whether or not a user can enroll a fingerprint */ +interface CanEnrollFingerprintsInteractor { + /** Returns true if a user can enroll a fingerprint false otherwise. */ + val canEnrollFingerprints: Flow + /** Indicates the maximum fingerprints enrollable for a given user */ + fun maxFingerprintsEnrollable(): Int +} diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/EnrollFingerprintInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/EnrollFingerprintInteractor.kt new file mode 100644 index 00000000000..be7b4d042ee --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/EnrollFingerprintInteractor.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 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.lib.domain.interactor + +import android.hardware.fingerprint.FingerprintEnrollOptions +import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason +import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState +import kotlinx.coroutines.flow.Flow + +/** Interactor that enrolls a fingerprint */ +interface EnrollFingerprintInteractor { + /** + * Runs [FingerprintManager.enroll] with the [hardwareAuthToken] and [EnrollReason] for this + * enrollment. If successful data in the [fingerprintEnrollState] should be populated. + */ + suspend fun enroll( + hardwareAuthToken: ByteArray?, + enrollReason: EnrollReason, + fingerprintEnrollOptions: FingerprintEnrollOptions, + ): Flow +} diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/EnrolledFingerprintsInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/EnrolledFingerprintsInteractor.kt new file mode 100644 index 00000000000..14fc1e5e215 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/EnrolledFingerprintsInteractor.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 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.lib.domain.interactor + +import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData +import kotlinx.coroutines.flow.Flow + +/** Interface to obtain the enrolled fingerprints */ +interface EnrolledFingerprintsInteractor { + /** Returns the list of current fingerprints. */ + val enrolledFingerprints: Flow?> +} diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/FingerprintManagerInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/FingerprintManagerInteractor.kt deleted file mode 100644 index 5f4cecabab1..00000000000 --- a/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/FingerprintManagerInteractor.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2024 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.lib.domain.interactor - -import android.hardware.fingerprint.FingerprintEnrollOptions -import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason -import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState -import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel -import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData -import com.android.systemui.biometrics.shared.model.FingerprintSensor -import kotlinx.coroutines.flow.Flow - -/** - * Interface to obtain the necessary data for FingerprintEnrollment/Settings - * - * Note that this interface should not have dependencies on heavyweight libraries such as the - * framework, hidl/aidl, etc. This makes it much easier to test and create fakes for. - */ -interface FingerprintManagerInteractor { - /** Returns the list of current fingerprints. */ - val enrolledFingerprints: Flow?> - - /** Returns the max enrollable fingerprints, note during SUW this might be 1 */ - val maxEnrollableFingerprints: Flow - - /** Returns true if a user can enroll a fingerprint false otherwise. */ - val canEnrollFingerprints: Flow - - /** Retrieves the sensor properties of a device */ - val sensorPropertiesInternal: Flow - - /** Runs the authenticate flow */ - suspend fun authenticate(): FingerprintAuthAttemptModel - - /** - * Generates a challenge with the provided [gateKeeperPasswordHandle] and on success returns a - * challenge and challenge token. This info can be used for secure operations such as enrollment - * - * @param gateKeeperPasswordHandle GateKeeper password handle generated by a Confirm - * @return A [Pair] of the challenge and challenge token - */ - suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair - - /** - * Runs [FingerprintManager.enroll] with the [hardwareAuthToken] and [EnrollReason] for this - * enrollment. If successful data in the [fingerprintEnrollState] should be populated. - */ - suspend fun enroll( - hardwareAuthToken: ByteArray?, - enrollReason: EnrollReason, - fingerprintEnrollOptions: FingerprintEnrollOptions, - ): Flow - - /** - * Removes the given fingerprint, returning true if it was successfully removed and false - * otherwise - */ - suspend fun removeFingerprint(fp: FingerprintData): Boolean - - /** Renames the given fingerprint if one exists */ - 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/lib/domain/interactor/GenerateChallengeInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/GenerateChallengeInteractor.kt new file mode 100644 index 00000000000..82667fe0465 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/GenerateChallengeInteractor.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 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.lib.domain.interactor + +/** This interactor is responsible for generating a challenge. */ +interface GenerateChallengeInteractor { + /** + * Generates a challenge with the provided [gateKeeperPasswordHandle] and on success returns a + * challenge and challenge token. This info can be used for secure operations such as enrollment + * + * @param gateKeeperPasswordHandle GateKeeper password handle generated by a Confirm + * @return A [Pair] of the challenge and challenge token + */ + suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair +} diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/RemoveFingerprintInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/RemoveFingerprintInteractor.kt new file mode 100644 index 00000000000..6d0e5641079 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/RemoveFingerprintInteractor.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 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.lib.domain.interactor + +import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData + +/** Interactor in charge of removing a fingerprint */ +interface RemoveFingerprintInteractor { + /** + * Removes the given fingerprint, returning true if it was successfully removed and false + * otherwise + */ + suspend fun removeFingerprint(fp: FingerprintData): Boolean +} diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/RenameFingerprintInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/RenameFingerprintInteractor.kt new file mode 100644 index 00000000000..d7fe1c0850f --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/RenameFingerprintInteractor.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 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.lib.domain.interactor + +import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData + +/** Interactor that can rename a fingerprint. */ +interface RenameFingerprintInteractor { + /** Renames the given fingerprint if one exists */ + suspend fun renameFingerprint(fp: FingerprintData, newName: String) +} diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/SensorInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/SensorInteractor.kt new file mode 100644 index 00000000000..f265c327af3 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/SensorInteractor.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 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.lib.domain.interactor + +import com.android.systemui.biometrics.shared.model.FingerprintSensor +import kotlinx.coroutines.flow.Flow + +/** Interactor that has various information about a fingerprint sensor */ +interface SensorInteractor { + /** Retrieves the sensor properties of the device */ + val sensorPropertiesInternal: Flow + /** Indicates if the device supports side fps */ + val hasSideFps: Flow +} 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 421548fabf9..77d070e5026 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 @@ -96,8 +96,8 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() { } /** - * View models below this line are not used by this class but must be initialized - * in the activity view model store to be used by other view models. + * View models below this line are not used by this class but must be initialized in the activity + * view model store to be used by other view models. */ private val fingerprintEnrollViewModel: FingerprintEnrollViewModel by viewModels { FingerprintEnrollViewModel.Factory 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 index c95020dff3c..932c40839af 100644 --- 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 @@ -25,7 +25,7 @@ import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import com.android.settings.SettingsApplication import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor -import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintAction import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel @@ -46,10 +46,10 @@ import kotlinx.coroutines.flow.update /** View Model used by the rear fingerprint enrollment fragment. */ class RFPSViewModel( - private val fingerprintEnrollViewModel: FingerprintEnrollEnrollingViewModel, - private val navigationViewModel: FingerprintNavigationViewModel, - orientationInteractor: OrientationInteractor, - private val fingerprintManager: FingerprintManagerInteractor, + private val fingerprintEnrollViewModel: FingerprintEnrollEnrollingViewModel, + private val navigationViewModel: FingerprintNavigationViewModel, + orientationInteractor: OrientationInteractor, + private val sensorInteractor: SensorInteractor, ) : ViewModel() { private val _textViewIsVisible = MutableStateFlow(false) @@ -62,7 +62,7 @@ class RFPSViewModel( val shouldAnimateIcon = _shouldAnimateIcon private var enrollFlow: Flow = - fingerprintManager.sensorPropertiesInternal.filterNotNull().combine( + sensorInteractor.sensorPropertiesInternal.filterNotNull().combine( fingerprintEnrollViewModel.enrollFlow ) { props, enroll -> if (props.sensorType == FingerprintSensorType.REAR) { @@ -181,7 +181,7 @@ class RFPSViewModel( provider[FingerprintEnrollEnrollingViewModel::class], provider[FingerprintNavigationViewModel::class], biometricEnvironment.orientationInteractor, - biometricEnvironment.fingerprintManagerInteractor, + biometricEnvironment.createSensorPropertiesInteractor(), ) } } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsViewModel.kt index 3396cdc5243..658c6c747c2 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsViewModel.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsViewModel.kt @@ -38,7 +38,7 @@ import com.android.settings.biometrics.fingerprint2.domain.interactor.Orientatio import com.android.settings.biometrics.fingerprint2.domain.interactor.TouchEventInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor -import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model.DescriptionText import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model.HeaderText @@ -76,17 +76,17 @@ class UdfpsViewModel( enrollStageInteractor: EnrollStageInteractor, orientationInteractor: OrientationInteractor, udfpsEnrollInteractor: UdfpsEnrollInteractor, - fingerprintManager: FingerprintManagerInteractor, accessibilityInteractor: AccessibilityInteractor, sensorRepository: FingerprintSensorInteractor, touchEventInteractor: TouchEventInteractor, + sensorInteractor: SensorInteractor, ) : ViewModel() { private val isSetupWizard = flowOf(false) private var shouldResetErollment = false private var _enrollState: Flow = - fingerprintManager.sensorPropertiesInternal.filterNotNull().combine( + sensorInteractor.sensorPropertiesInternal.filterNotNull().combine( fingerprintEnrollEnrollingViewModel.enrollFlow ) { props, enroll -> if (props.sensorType.isUdfps()) { @@ -198,8 +198,7 @@ class UdfpsViewModel( .distinctUntilChanged() private val _touchEvent: MutableStateFlow = MutableStateFlow(null) - val touchEvent = - _touchEvent.asStateFlow().filterNotNull() + val touchEvent = _touchEvent.asStateFlow().filterNotNull() /** Determines the current [EnrollStageModel] enrollment is in */ private val enrollStage: Flow = @@ -267,11 +266,7 @@ class UdfpsViewModel( backgroundViewModel.background.filter { it }.collect { didGoToBackground() } } - viewModelScope.launch { - touchEventInteractor.touchEvent.collect { - _touchEvent.update { it } - } - } + viewModelScope.launch { touchEventInteractor.touchEvent.collect { _touchEvent.update { it } } } } /** Indicates if we should show the lottie. */ @@ -430,10 +425,10 @@ class UdfpsViewModel( biometricEnvironment.enrollStageInteractor, biometricEnvironment.orientationInteractor, biometricEnvironment.udfpsEnrollInteractor, - biometricEnvironment.fingerprintManagerInteractor, biometricEnvironment.accessibilityInteractor, biometricEnvironment.sensorInteractor, biometricEnvironment.touchEventInteractor, + biometricEnvironment.createSensorPropertiesInteractor(), ) } } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollConfirmationViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollConfirmationViewModel.kt index 5ce2ed756d8..0803f890a48 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollConfirmationViewModel.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollConfirmationViewModel.kt @@ -16,27 +16,27 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel -import android.util.Log import androidx.lifecycle.VIEW_MODEL_STORE_OWNER_KEY import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import com.android.settings.SettingsApplication -import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor import kotlinx.coroutines.flow.Flow /** Models the UI state for [FingerprintEnrollConfirmationV2Fragment] */ class FingerprintEnrollConfirmationViewModel( private val navigationViewModel: FingerprintNavigationViewModel, - fingerprintInteractor: FingerprintManagerInteractor, + private val canEnrollFingerprintsInteractor: CanEnrollFingerprintsInteractor, ) : ViewModel() { /** * Indicates if the add another button is possible. This should only be true when the user is able * to enroll more fingerprints. */ - val isAddAnotherButtonVisible: Flow = fingerprintInteractor.canEnrollFingerprints + val isAddAnotherButtonVisible: Flow = + canEnrollFingerprintsInteractor.canEnrollFingerprints /** * Indicates that the user has clicked the next button and is done with fingerprint enrollment. @@ -64,7 +64,7 @@ class FingerprintEnrollConfirmationViewModel( val provider = ViewModelProvider(this[VIEW_MODEL_STORE_OWNER_KEY]!!) FingerprintEnrollConfirmationViewModel( provider[FingerprintNavigationViewModel::class], - biometricEnvironment!!.fingerprintManagerInteractor, + biometricEnvironment!!.createCanEnrollFingerprintsInteractor(), ) } } 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 3568dbdd511..9b2cdde7cef 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 @@ -27,7 +27,7 @@ import com.android.settings.SettingsApplication import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor -import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Education @@ -44,20 +44,20 @@ import kotlinx.coroutines.launch /** Models the UI state for fingerprint enroll education */ class FingerprintEnrollFindSensorViewModel( - private val navigationViewModel: FingerprintNavigationViewModel, - private val fingerprintEnrollViewModel: FingerprintEnrollViewModel, - private val gatekeeperViewModel: FingerprintGatekeeperViewModel, - backgroundViewModel: BackgroundViewModel, - fingerprintFlowViewModel: FingerprintFlowViewModel, - accessibilityInteractor: AccessibilityInteractor, - foldStateInteractor: FoldStateInteractor, - orientationInteractor: OrientationInteractor, - fingerprintManagerInteractor: FingerprintManagerInteractor, + private val navigationViewModel: FingerprintNavigationViewModel, + private val fingerprintEnrollViewModel: FingerprintEnrollViewModel, + private val gatekeeperViewModel: FingerprintGatekeeperViewModel, + backgroundViewModel: BackgroundViewModel, + fingerprintFlowViewModel: FingerprintFlowViewModel, + accessibilityInteractor: AccessibilityInteractor, + foldStateInteractor: FoldStateInteractor, + orientationInteractor: OrientationInteractor, + sensorInteractor: SensorInteractor, ) : ViewModel() { /** Represents the stream of sensor type. */ val sensorType: Flow = - fingerprintManagerInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType } + sensorInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType } private val _isUdfps: Flow = sensorType.map { it == FingerprintSensorType.UDFPS_OPTICAL || it == FingerprintSensorType.UDFPS_ULTRASONIC @@ -216,7 +216,7 @@ class FingerprintEnrollFindSensorViewModel( biometricEnvironment.accessibilityInteractor, biometricEnvironment.foldStateInteractor, biometricEnvironment.orientationInteractor, - biometricEnvironment.fingerprintManagerInteractor, + biometricEnvironment.createSensorPropertiesInteractor(), ) } } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollIntroViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollIntroViewModel.kt index 6ec204815e1..e103cbc47fc 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollIntroViewModel.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollIntroViewModel.kt @@ -22,7 +22,7 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import com.android.settings.SettingsApplication -import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Introduction import com.android.systemui.biometrics.shared.model.FingerprintSensor @@ -30,13 +30,13 @@ import kotlinx.coroutines.flow.Flow /** A view model for fingerprint enroll introduction. */ class FingerprintEnrollIntroViewModel( - val navigationViewModel: FingerprintNavigationViewModel, - fingerprintFlowViewModel: FingerprintFlowViewModel, - fingerprintManagerInteractor: FingerprintManagerInteractor, + val navigationViewModel: FingerprintNavigationViewModel, + fingerprintFlowViewModel: FingerprintFlowViewModel, + sensorInteractor: SensorInteractor, ) : ViewModel() { /** Represents a stream of [FingerprintSensor] */ - val sensor: Flow = fingerprintManagerInteractor.sensorPropertiesInternal + val sensor: Flow = sensorInteractor.sensorPropertiesInternal /** Represents a stream of [FingerprintFlow] */ val fingerprintFlow: Flow = fingerprintFlowViewModel.fingerprintFlow @@ -67,7 +67,7 @@ class FingerprintEnrollIntroViewModel( FingerprintEnrollIntroViewModel( provider[FingerprintNavigationViewModel::class], provider[FingerprintFlowViewModel::class], - biometricEnvironment!!.fingerprintManagerInteractor, + biometricEnvironment!!.createSensorPropertiesInteractor(), ) } } 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 2669b8bbe5b..fb8a182ab40 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 @@ -24,7 +24,8 @@ import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import com.android.settings.SettingsApplication -import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrollFingerprintInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Education @@ -42,9 +43,10 @@ import kotlinx.coroutines.flow.update /** Represents all of the fingerprint information needed for a fingerprint enrollment process. */ class FingerprintEnrollViewModel( - private val fingerprintManagerInteractor: FingerprintManagerInteractor, - gatekeeperViewModel: FingerprintGatekeeperViewModel, - val navigationViewModel: FingerprintNavigationViewModel, + gatekeeperViewModel: FingerprintGatekeeperViewModel, + val navigationViewModel: FingerprintNavigationViewModel, + private val sensorInteractor: SensorInteractor, + private val fingerprintEnrollInteractor: EnrollFingerprintInteractor, ) : ViewModel() { /** @@ -67,7 +69,7 @@ class FingerprintEnrollViewModel( /** Represents the stream of [FingerprintSensorType] */ val sensorType: Flow = - fingerprintManagerInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType } + sensorInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType } /** * A flow that contains a [FingerprintEnrollViewModel] which contains the relevant information for @@ -90,7 +92,7 @@ class FingerprintEnrollViewModel( enrollReason != null && enrollOptions != null ) { - fingerprintManagerInteractor + fingerprintEnrollInteractor .enroll(hardwareAuthToken.token, enrollReason, enrollOptions) .collect { emit(it) } } @@ -137,9 +139,10 @@ class FingerprintEnrollViewModel( val biometricEnvironment = settingsApplication.biometricEnvironment val provider = ViewModelProvider(this[VIEW_MODEL_STORE_OWNER_KEY]!!) FingerprintEnrollViewModel( - biometricEnvironment!!.fingerprintManagerInteractor, provider[FingerprintGatekeeperViewModel::class], provider[FingerprintNavigationViewModel::class], + biometricEnvironment!!.createSensorPropertiesInteractor(), + biometricEnvironment!!.createFingerprintEnrollInteractor(), ) } } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintGatekeeperViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintGatekeeperViewModel.kt index b5be165056e..c2b0a0f23d9 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintGatekeeperViewModel.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintGatekeeperViewModel.kt @@ -24,7 +24,7 @@ import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import com.android.settings.SettingsApplication -import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -50,7 +50,7 @@ sealed interface GatekeeperInfo { * in as a parameter to this class. */ class FingerprintGatekeeperViewModel( - private val fingerprintManagerInteractor: FingerprintManagerInteractor + private val generateChallengeInteractor: GenerateChallengeInteractor ) : ViewModel() { private var _gatekeeperInfo: MutableStateFlow = MutableStateFlow(null) @@ -78,7 +78,7 @@ class FingerprintGatekeeperViewModel( _gatekeeperInfo.update { GatekeeperInfo.Invalid } } else { viewModelScope.launch { - val res = fingerprintManagerInteractor.generateChallenge(theGatekeeperPasswordHandle!!) + val res = generateChallengeInteractor.generateChallenge(theGatekeeperPasswordHandle!!) _gatekeeperInfo.update { GatekeeperInfo.GatekeeperPasswordInfo(res.second, res.first) } if (shouldStartTimer) { startTimeout() @@ -119,7 +119,7 @@ class FingerprintGatekeeperViewModel( val settingsApplication = this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as SettingsApplication val biometricEnvironment = settingsApplication.biometricEnvironment - FingerprintGatekeeperViewModel(biometricEnvironment!!.fingerprintManagerInteractor) + FingerprintGatekeeperViewModel(biometricEnvironment!!.createGenerateChallengeInteractor()) } } } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintNavigationViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintNavigationViewModel.kt index caf7d2a8a9b..d9bcf7fd901 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintNavigationViewModel.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintNavigationViewModel.kt @@ -23,7 +23,7 @@ import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import com.android.settings.SettingsApplication -import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Finish import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.TransitionStep @@ -46,7 +46,7 @@ import kotlinx.coroutines.flow.update * fragments/viewmodels that want to consume these events. It should provide no additional * functionality beyond what is available in [FingerprintNavigationStep]. */ -class FingerprintNavigationViewModel(fingerprintManagerInteractor: FingerprintManagerInteractor) : +class FingerprintNavigationViewModel(sensorInteractor: SensorInteractor) : ViewModel() { private val _flowInternal: MutableStateFlow = MutableStateFlow(null) @@ -55,7 +55,7 @@ class FingerprintNavigationViewModel(fingerprintManagerInteractor: FingerprintMa combine( _flowInternal, _hasConfirmedDeviceCredential, - fingerprintManagerInteractor.sensorPropertiesInternal, + sensorInteractor.sensorPropertiesInternal, ) { flow, hasConfirmed, sensorType -> if (flow == null || sensorType == null) { return@combine null @@ -144,7 +144,7 @@ class FingerprintNavigationViewModel(fingerprintManagerInteractor: FingerprintMa val settingsApplication = this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as SettingsApplication val biometricEnvironment = settingsApplication.biometricEnvironment - FingerprintNavigationViewModel(biometricEnvironment!!.fingerprintManagerInteractor) + FingerprintNavigationViewModel(biometricEnvironment!!.createSensorPropertiesInteractor()) } } } 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 4c3773bb3e9..241eaea0b28 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 @@ -35,19 +35,16 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import androidx.preference.PreferenceCategory -import com.android.internal.widget.LockPatternUtils import com.android.settings.R +import com.android.settings.SettingsApplication 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.EXTRA_FROM_SETTINGS_SUMMARY import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED -import com.android.settings.biometrics.GatekeeperPasswordProvider import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepositoryImpl -import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl -import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl import com.android.settings.biometrics.fingerprint2.domain.interactor.PressToAuthInteractorImpl import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData @@ -223,35 +220,24 @@ class FingerprintSettingsV2Fragment : val fingerprintSensorProvider = FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, lifecycleScope) val pressToAuthInteractor = PressToAuthInteractorImpl(context, backgroundDispatcher) - val fingerprintEnrollStateRepository = - FingerprintEnrollInteractorImpl( - requireContext().applicationContext, - fingerprintManager, - Settings, - ) - - val interactor = - FingerprintManagerInteractorImpl( - context.applicationContext, - backgroundDispatcher, - fingerprintManager, - fingerprintSensorProvider, - GatekeeperPasswordProvider(LockPatternUtils(context.applicationContext)), - fingerprintEnrollStateRepository, - ) val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN) val challenge = intent.getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, -1L) + val application = requireActivity().application as SettingsApplication + val environment = + application.biometricEnvironment + ?: throw IllegalStateException("The biometric environment must be present") navigationViewModel = ViewModelProvider( this, FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory( userId, - interactor, backgroundDispatcher, token, challenge, + environment.createFingerprintsEnrolledInteractor(), + environment.createGenerateChallengeInteractor(), ), )[FingerprintSettingsNavigationViewModel::class.java] @@ -260,9 +246,14 @@ class FingerprintSettingsV2Fragment : this, FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory( userId, - interactor, backgroundDispatcher, navigationViewModel, + environment.createCanEnrollFingerprintsInteractor(), + environment.createSensorPropertiesInteractor(), + environment.createAuthenticateInteractor(), + environment.createRenameFingerprintInteractor(), + environment.createRemoveFingerprintInteractor(), + environment.createFingerprintsEnrolledInteractor(), ), )[FingerprintSettingsViewModel::class.java] diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsNavigationViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsNavigationViewModel.kt index 8a694aeac91..73b2b1cd3d3 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsNavigationViewModel.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsNavigationViewModel.kt @@ -21,7 +21,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.android.settings.biometrics.BiometricEnrollBase -import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrolledFingerprintsInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -33,10 +34,11 @@ import kotlinx.coroutines.launch /** A Viewmodel that represents the navigation of the FingerprintSettings activity. */ class FingerprintSettingsNavigationViewModel( private val userId: Int, - private val fingerprintManagerInteractor: FingerprintManagerInteractor, private val backgroundDispatcher: CoroutineDispatcher, tokenInit: ByteArray?, challengeInit: Long?, + private val enrolledFingerprintsInteractor: EnrolledFingerprintsInteractor, + private val generateChallengeInteractor: GenerateChallengeInteractor, ) : ViewModel() { private var token = tokenInit @@ -52,7 +54,7 @@ class FingerprintSettingsNavigationViewModel( _nextStep.update { LaunchConfirmDeviceCredential(userId) } } else { viewModelScope.launch { - if (fingerprintManagerInteractor.enrolledFingerprints.last()?.isEmpty() == true) { + if (enrolledFingerprintsInteractor.enrolledFingerprints.last()?.isEmpty() == true) { _nextStep.update { EnrollFirstFingerprint(userId, null, challenge, token) } } else { showSettingsHelper() @@ -148,13 +150,13 @@ class FingerprintSettingsNavigationViewModel( } private suspend fun launchEnrollNextStep(gateKeeperPasswordHandle: Long?) { - fingerprintManagerInteractor.enrolledFingerprints.collect { + enrolledFingerprintsInteractor.enrolledFingerprints.collect { if (it?.isEmpty() == true) { _nextStep.update { EnrollFirstFingerprint(userId, gateKeeperPasswordHandle, null, null) } } else { viewModelScope.launch(backgroundDispatcher) { val challengePair = - fingerprintManagerInteractor.generateChallenge(gateKeeperPasswordHandle!!) + generateChallengeInteractor.generateChallenge(gateKeeperPasswordHandle!!) challenge = challengePair.first token = challengePair.second @@ -174,10 +176,11 @@ class FingerprintSettingsNavigationViewModel( class FingerprintSettingsNavigationModelFactory( private val userId: Int, - private val interactor: FingerprintManagerInteractor, private val backgroundDispatcher: CoroutineDispatcher, private val token: ByteArray?, private val challenge: Long?, + private val enrolledFingerprintsInteractor: EnrolledFingerprintsInteractor, + private val generateChallengeInteractor: GenerateChallengeInteractor, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -185,10 +188,11 @@ class FingerprintSettingsNavigationViewModel( return FingerprintSettingsNavigationViewModel( userId, - interactor, backgroundDispatcher, token, challenge, + enrolledFingerprintsInteractor, + generateChallengeInteractor, ) as T } 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 cf8c527d6d4..c306c7870b9 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 @@ -21,7 +21,12 @@ import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope -import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.AuthenitcateInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrolledFingerprintsInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RemoveFingerprintInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RenameFingerprintInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData import com.android.systemui.biometrics.shared.model.FingerprintSensorType @@ -49,9 +54,14 @@ private const val DEBUG = false /** Models the UI state for fingerprint settings. */ class FingerprintSettingsViewModel( private val userId: Int, - private val fingerprintManagerInteractor: FingerprintManagerInteractor, private val backgroundDispatcher: CoroutineDispatcher, private val navigationViewModel: FingerprintSettingsNavigationViewModel, + private val canEnrollFingerprintsInteractor: CanEnrollFingerprintsInteractor, + private val sensorInteractor: SensorInteractor, + private val authenticateInteractor: AuthenitcateInteractor, + private val renameFingerprintInteractor: RenameFingerprintInteractor, + private val removeFingerprintInteractor: RemoveFingerprintInteractor, + private val enrolledFingerprintsInteractor: EnrolledFingerprintsInteractor, ) : ViewModel() { private val _enrolledFingerprints: MutableStateFlow?> = MutableStateFlow(null) @@ -62,19 +72,18 @@ class FingerprintSettingsViewModel( /** Represents the stream of the information of "Add Fingerprint" preference. */ val addFingerprintPrefInfo: Flow> = - _enrolledFingerprints.filterOnlyWhenSettingsIsShown().transform { - emit( - Pair( - fingerprintManagerInteractor.canEnrollFingerprints.first(), - fingerprintManagerInteractor.maxEnrollableFingerprints.first(), - ) - ) + _enrolledFingerprints.filterOnlyWhenSettingsIsShown().combine( + canEnrollFingerprintsInteractor.canEnrollFingerprints + ) { _, canEnrollFingerprints -> + Pair(canEnrollFingerprints, canEnrollFingerprintsInteractor.maxFingerprintsEnrollable()) } /** Represents the stream of visibility of sfps preference. */ val isSfpsPrefVisible: Flow = - _enrolledFingerprints.filterOnlyWhenSettingsIsShown().transform { - emit(fingerprintManagerInteractor.hasSideFps() == true && !it.isNullOrEmpty()) + _enrolledFingerprints.filterOnlyWhenSettingsIsShown().combine(sensorInteractor.hasSideFps) { + fingerprints, + hasSideFps -> + hasSideFps && !fingerprints.isNullOrEmpty() } private val _isShowingDialog: MutableStateFlow = MutableStateFlow(null) @@ -90,10 +99,10 @@ class FingerprintSettingsViewModel( private val _consumerShouldAuthenticate: MutableStateFlow = MutableStateFlow(false) private val _fingerprintSensorType: Flow = - fingerprintManagerInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType } + sensorInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType } private val _sensorNullOrEmpty: Flow = - fingerprintManagerInteractor.sensorPropertiesInternal.map { it == null } + sensorInteractor.sensorPropertiesInternal.map { it == null } private val _isLockedOut: MutableStateFlow = MutableStateFlow(null) @@ -172,7 +181,7 @@ class FingerprintSettingsViewModel( while (it && navigationViewModel.nextStep.value is ShowSettings) { Log.d(TAG, "canAuthenticate authing") attemptingAuth() - when (val authAttempt = fingerprintManagerInteractor.authenticate()) { + when (val authAttempt = authenticateInteractor.authenticate()) { is FingerprintAuthAttemptModel.Success -> { onAuthSuccess(authAttempt) emit(authAttempt) @@ -243,7 +252,7 @@ class FingerprintSettingsViewModel( /** A request to delete a fingerprint */ fun deleteFingerprint(fp: FingerprintData) { viewModelScope.launch(backgroundDispatcher) { - if (fingerprintManagerInteractor.removeFingerprint(fp)) { + if (removeFingerprintInteractor.removeFingerprint(fp)) { updateEnrolledFingerprints() } } @@ -252,7 +261,7 @@ class FingerprintSettingsViewModel( /** A request to rename a fingerprint */ fun renameFingerprint(fp: FingerprintData, newName: String) { viewModelScope.launch { - fingerprintManagerInteractor.renameFingerprint(fp, newName) + renameFingerprintInteractor.renameFingerprint(fp, newName) updateEnrolledFingerprints() } } @@ -271,7 +280,7 @@ class FingerprintSettingsViewModel( } private suspend fun updateEnrolledFingerprints() { - _enrolledFingerprints.update { fingerprintManagerInteractor.enrolledFingerprints.first() } + _enrolledFingerprints.update { enrolledFingerprintsInteractor.enrolledFingerprints.first() } } /** Used to indicate whether the consumer of the view model is ready for authentication. */ @@ -288,9 +297,14 @@ class FingerprintSettingsViewModel( class FingerprintSettingsViewModelFactory( private val userId: Int, - private val interactor: FingerprintManagerInteractor, private val backgroundDispatcher: CoroutineDispatcher, private val navigationViewModel: FingerprintSettingsNavigationViewModel, + private val canEnrollFingerprintsInteractor: CanEnrollFingerprintsInteractor, + private val sensorInteractor: SensorInteractor, + private val authenticateInteractor: AuthenitcateInteractor, + private val renameFingerprintInteractor: RenameFingerprintInteractor, + private val removeFingerprintInteractor: RemoveFingerprintInteractor, + private val enrolledFingerprintsInteractor: EnrolledFingerprintsInteractor, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -298,9 +312,14 @@ class FingerprintSettingsViewModel( return FingerprintSettingsViewModel( userId, - interactor, backgroundDispatcher, navigationViewModel, + canEnrollFingerprintsInteractor, + sensorInteractor, + authenticateInteractor, + renameFingerprintInteractor, + removeFingerprintInteractor, + enrolledFingerprintsInteractor, ) as T } diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java index f451f0a7225..09940b3d7f5 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java @@ -20,6 +20,7 @@ import android.app.settings.SettingsEnums; import android.content.Context; import android.content.res.Configuration; import android.graphics.drawable.Drawable; +import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.text.TextUtils; @@ -46,9 +47,13 @@ import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnCreate; import com.android.settingslib.core.lifecycle.events.OnDestroy; import com.android.settingslib.core.lifecycle.events.OnResume; +import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState; import com.android.settingslib.widget.FooterPreference; +import com.android.settingslib.widget.SettingsSpinnerAdapter; +import com.android.settingslib.widget.SettingsSpinnerPreference; import java.util.ArrayList; import java.util.List; @@ -58,7 +63,7 @@ import java.util.Set; /** Controller for battery usage breakdown preference group. */ public class BatteryUsageBreakdownController extends BasePreferenceController - implements LifecycleObserver, OnResume, OnDestroy { + implements LifecycleObserver, OnResume, OnDestroy, OnCreate, OnSaveInstanceState { private static final String TAG = "BatteryUsageBreakdownController"; private static final String ROOT_PREFERENCE_KEY = "battery_usage_breakdown"; private static final String FOOTER_PREFERENCE_KEY = "battery_usage_footer"; @@ -67,6 +72,7 @@ public class BatteryUsageBreakdownController extends BasePreferenceController private static final String PACKAGE_NAME_NONE = "none"; private static final String SLOT_TIMESTAMP = "slot_timestamp"; private static final String ANOMALY_KEY = "anomaly_key"; + private static final String KEY_SPINNER_POSITION = "spinner_position"; private static final List EMPTY_ENTRY_LIST = new ArrayList<>(); private static int sUiMode = Configuration.UI_MODE_NIGHT_UNDEFINED; @@ -78,12 +84,12 @@ public class BatteryUsageBreakdownController extends BasePreferenceController @VisibleForTesting final Map mPreferenceCache = new ArrayMap<>(); - private int mSpinnerPosition; private String mSlotInformation; + private SettingsSpinnerPreference mSpinnerPreference; + private SettingsSpinnerAdapter mSpinnerAdapter; @VisibleForTesting Context mPrefContext; @VisibleForTesting PreferenceCategory mRootPreference; - @VisibleForTesting SpinnerPreference mSpinnerPreference; @VisibleForTesting PreferenceGroup mAppListPreferenceGroup; @VisibleForTesting FooterPreference mFooterPreference; @VisibleForTesting BatteryDiffData mBatteryDiffData; @@ -92,6 +98,7 @@ public class BatteryUsageBreakdownController extends BasePreferenceController @VisibleForTesting String mPercentLessThanThresholdContentDescription; @VisibleForTesting boolean mIsHighlightSlot; @VisibleForTesting int mAnomalyKeyNumber; + @VisibleForTesting int mSpinnerPosition; @VisibleForTesting String mAnomalyEntryKey; @VisibleForTesting String mAnomalyHintString; @VisibleForTesting String mAnomalyHintPrefKey; @@ -110,6 +117,15 @@ public class BatteryUsageBreakdownController extends BasePreferenceController } } + @Override + public void onCreate(Bundle savedInstanceState) { + if (savedInstanceState == null) { + return; + } + mSpinnerPosition = savedInstanceState.getInt(KEY_SPINNER_POSITION, mSpinnerPosition); + Log.d(TAG, "onCreate() spinnerPosition=" + mSpinnerPosition); + } + @Override public void onResume() { final int currentUiMode = @@ -140,6 +156,15 @@ public class BatteryUsageBreakdownController extends BasePreferenceController return false; } + @Override + public void onSaveInstanceState(Bundle savedInstanceState) { + if (savedInstanceState == null) { + return; + } + savedInstanceState.putInt(KEY_SPINNER_POSITION, mSpinnerPosition); + Log.d(TAG, "onSaveInstanceState() spinnerPosition=" + mSpinnerPosition); + } + private boolean isAnomalyBatteryDiffEntry(BatteryDiffEntry entry) { return mIsHighlightSlot && mAnomalyEntryKey != null @@ -218,11 +243,14 @@ public class BatteryUsageBreakdownController extends BasePreferenceController formatPercentage); mAppListPreferenceGroup.setOrderingAsAdded(false); - mSpinnerPreference.initializeSpinner( + mSpinnerAdapter = new SettingsSpinnerAdapter<>(mPrefContext); + mSpinnerAdapter.addAll( new String[] { mPrefContext.getString(R.string.battery_usage_spinner_view_by_apps), mPrefContext.getString(R.string.battery_usage_spinner_view_by_systems) - }, + }); + mSpinnerPreference.setAdapter(mSpinnerAdapter); + mSpinnerPreference.setOnItemSelectedListener( new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected( @@ -244,6 +272,7 @@ public class BatteryUsageBreakdownController extends BasePreferenceController @Override public void onNothingSelected(AdapterView parent) {} }); + mSpinnerPreference.setSelection(mSpinnerPosition); } /** diff --git a/src/com/android/settings/fuelgauge/batteryusage/SpinnerPreference.java b/src/com/android/settings/fuelgauge/batteryusage/SpinnerPreference.java deleted file mode 100644 index 886d00de67e..00000000000 --- a/src/com/android/settings/fuelgauge/batteryusage/SpinnerPreference.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * 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.batteryusage; - -import android.content.Context; -import android.os.Parcelable; -import android.util.AttributeSet; -import android.util.Log; -import android.widget.AdapterView; -import android.widget.Spinner; - -import androidx.preference.Preference; -import androidx.preference.PreferenceViewHolder; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.settings.R; -import com.android.settingslib.widget.SettingsSpinnerAdapter; - -/** A preference which contains a spinner. */ -public class SpinnerPreference extends Preference { - private static final String TAG = "SpinnerPreference"; - - private AdapterView.OnItemSelectedListener mOnItemSelectedListener; - - @VisibleForTesting Spinner mSpinner; - @VisibleForTesting String[] mItems; - @VisibleForTesting int mSavedSpinnerPosition; - - public SpinnerPreference(Context context, AttributeSet attrs) { - super(context, attrs); - setLayoutResource(R.layout.preference_spinner); - } - - void initializeSpinner( - String[] items, AdapterView.OnItemSelectedListener onItemSelectedListener) { - mItems = items; - mOnItemSelectedListener = onItemSelectedListener; - } - - @Override - public void onBindViewHolder(PreferenceViewHolder view) { - if (mSpinner != null) { - return; - } - - mSpinner = (Spinner) view.findViewById(R.id.spinner); - mSpinner.setAdapter(new SpinnerAdapter(getContext(), mItems)); - mSpinner.setSelection(mSavedSpinnerPosition); - mSpinner.setLongClickable(false); - if (mOnItemSelectedListener != null) { - mSpinner.setOnItemSelectedListener(mOnItemSelectedListener); - } - } - - @Override - protected Parcelable onSaveInstanceState() { - if (mSpinner == null) { - return super.onSaveInstanceState(); - } - Log.d(TAG, "onSaveInstanceState() spinnerPosition=" + mSpinner.getSelectedItemPosition()); - return new SavedState(super.onSaveInstanceState(), mSpinner.getSelectedItemPosition()); - } - - @Override - protected void onRestoreInstanceState(Parcelable state) { - if (state == null || state == BaseSavedState.EMPTY_STATE) { - super.onRestoreInstanceState(state); - return; - } - if (!(state instanceof SavedState)) { - // To avoid the IllegalArgumentException, return the BaseSavedState.EMPTY_STATE. - super.onRestoreInstanceState(BaseSavedState.EMPTY_STATE); - return; - } - SavedState savedState = (SavedState) state; - super.onRestoreInstanceState(savedState.getSuperState()); - mSavedSpinnerPosition = savedState.getSpinnerPosition(); - if (mOnItemSelectedListener != null) { - mOnItemSelectedListener.onItemSelected( - /* parent= */ null, - /* view= */ null, - savedState.getSpinnerPosition(), - /* id= */ 0); - } - Log.d(TAG, "onRestoreInstanceState() spinnerPosition=" + savedState.getSpinnerPosition()); - } - - @VisibleForTesting - static class SavedState extends BaseSavedState { - private int mSpinnerPosition; - - SavedState(Parcelable superState, int spinnerPosition) { - super(superState); - mSpinnerPosition = spinnerPosition; - } - - int getSpinnerPosition() { - return mSpinnerPosition; - } - } - - private static class SpinnerAdapter extends SettingsSpinnerAdapter { - private final String[] mItems; - - SpinnerAdapter(Context context, String[] items) { - super(context); - mItems = items; - } - - @Override - public int getCount() { - return mItems.length; - } - - @Override - public CharSequence getItem(int position) { - return mItems[position]; - } - } -} diff --git a/src/com/android/settings/notification/modes/AbstractZenModeHeaderController.java b/src/com/android/settings/notification/modes/AbstractZenModeHeaderController.java index 81b53cc3d3e..7cc67ccf9fc 100644 --- a/src/com/android/settings/notification/modes/AbstractZenModeHeaderController.java +++ b/src/com/android/settings/notification/modes/AbstractZenModeHeaderController.java @@ -61,13 +61,6 @@ abstract class AbstractZenModeHeaderController extends AbstractZenModePreference LayoutPreference preference = checkNotNull(screen.findPreference(getPreferenceKey())); preference.setSelectable(false); - if (mHeaderController == null) { - mHeaderController = EntityHeaderController.newInstance( - mFragment.getActivity(), - mFragment, - preference.findViewById(R.id.entity_header)); - } - ImageView iconView = checkNotNull(preference.findViewById(R.id.entity_header_icon)); ViewGroup.LayoutParams layoutParams = iconView.getLayoutParams(); if (layoutParams.width != iconSizePx || layoutParams.height != iconSizePx) { @@ -75,6 +68,14 @@ abstract class AbstractZenModeHeaderController extends AbstractZenModePreference layoutParams.height = iconSizePx; iconView.setLayoutParams(layoutParams); } + + if (mHeaderController == null) { + mHeaderController = EntityHeaderController.newInstance( + mFragment.getActivity(), + mFragment, + preference.findViewById(R.id.entity_header)); + mHeaderController.done(false); // Make the space for the (unused) name go away. + } } protected void updateIcon(Preference preference, @NonNull ZenMode zenMode, diff --git a/src/com/android/settings/notification/modes/CircularIconsPreference.java b/src/com/android/settings/notification/modes/CircularIconsPreference.java index ccf7f529e46..5a893527211 100644 --- a/src/com/android/settings/notification/modes/CircularIconsPreference.java +++ b/src/com/android/settings/notification/modes/CircularIconsPreference.java @@ -16,236 +16,72 @@ package com.android.settings.notification.modes; +import static android.view.View.GONE; +import static android.view.View.VISIBLE; + import static com.google.common.base.Preconditions.checkNotNull; import android.content.Context; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; import androidx.preference.PreferenceViewHolder; import com.android.settings.R; import com.android.settingslib.RestrictedPreference; import com.google.common.base.Equivalence; -import com.google.common.collect.ImmutableList; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; - -import java.util.List; -import java.util.concurrent.Executor; public class CircularIconsPreference extends RestrictedPreference { - private static final float DISABLED_ITEM_ALPHA = 0.3f; - - record LoadedIcons(ImmutableList icons, int extraItems) { - static final LoadedIcons EMPTY = new LoadedIcons(ImmutableList.of(), 0); - } - - private Executor mUiExecutor; - - // Chronologically, fields will be set top-to-bottom. - @Nullable private CircularIconSet mIconSet; - @Nullable private ListenableFuture> mPendingLoadIconsFuture; - @Nullable private LoadedIcons mLoadedIcons; + private CircularIconSet mIconSet = CircularIconSet.EMPTY; public CircularIconsPreference(Context context) { super(context); - init(context); - } - - @VisibleForTesting(otherwise = VisibleForTesting.NONE) - public CircularIconsPreference(Context context, Executor uiExecutor) { - this(context); - mUiExecutor = uiExecutor; + init(); } public CircularIconsPreference(Context context, AttributeSet attrs) { super(context, attrs); - init(context); + init(); } public CircularIconsPreference(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - init(context); + init(); } public CircularIconsPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - init(context); + init(); } - private void init(Context context) { - mUiExecutor = context.getMainExecutor(); + private void init() { setLayoutResource(R.layout.preference_circular_icons); } - void displayIcons(CircularIconSet iconSet) { - displayIcons(iconSet, null); + void setIcons(CircularIconSet iconSet) { + setIcons(iconSet, null); } - void displayIcons(CircularIconSet iconSet, @Nullable Equivalence itemEquivalence) { - if (mIconSet != null && mIconSet.hasSameItemsAs(iconSet, itemEquivalence)) { + void setIcons(CircularIconSet iconSet, @Nullable Equivalence itemEquivalence) { + if (mIconSet.hasSameItemsAs(iconSet, itemEquivalence)) { return; } + mIconSet = iconSet; - - mLoadedIcons = null; - if (mPendingLoadIconsFuture != null) { - mPendingLoadIconsFuture.cancel(true); - mPendingLoadIconsFuture = null; - } - notifyChanged(); } @Override public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); + CircularIconsView iconContainer = checkNotNull( + (CircularIconsView) holder.findViewById(R.id.circles_container)); - LinearLayout iconContainer = checkNotNull( - (LinearLayout) holder.findViewById(R.id.circles_container)); - bindIconContainer(iconContainer); - } - - private void bindIconContainer(LinearLayout container) { - if (mLoadedIcons != null) { - // We have the icons ready to display already, show them. - setDrawables(container, mLoadedIcons); - } else if (mIconSet != null) { - // We know what icons we want, but haven't yet loaded them. - if (mIconSet.size() == 0) { - container.setVisibility(View.GONE); - mLoadedIcons = LoadedIcons.EMPTY; - return; - } - container.setVisibility(View.VISIBLE); - if (container.getMeasuredWidth() != 0) { - startLoadingIcons(container, mIconSet); - } else { - container.getViewTreeObserver().addOnGlobalLayoutListener( - new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - container.getViewTreeObserver().removeOnGlobalLayoutListener(this); - notifyChanged(); - } - } - ); - } - } - } - - private void startLoadingIcons(LinearLayout container, CircularIconSet iconSet) { - Resources res = getContext().getResources(); - int availableSpace = container.getMeasuredWidth(); - int iconHorizontalSpace = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter) - + res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_margin_between); - int numIconsThatFit = availableSpace / iconHorizontalSpace; - - List> iconFutures; - int extraItems; - if (iconSet.size() > numIconsThatFit) { - // Reserve one space for the (+xx) textview. - int numIconsToShow = numIconsThatFit - 1; - if (numIconsToShow < 0) { - numIconsToShow = 0; - } - iconFutures = iconSet.getIcons(numIconsToShow); - extraItems = iconSet.size() - numIconsToShow; - } else { - // Fit exactly or with remaining space. - iconFutures = iconSet.getIcons(); - extraItems = 0; - } - - // Display icons when all are ready (more consistent than randomly loading). - mPendingLoadIconsFuture = Futures.allAsList(iconFutures); - FutureUtil.whenDone( - mPendingLoadIconsFuture, - icons -> { - mLoadedIcons = new LoadedIcons(ImmutableList.copyOf(icons), extraItems); - notifyChanged(); // So that view is rebound and icons actually shown. - }, - mUiExecutor); - } - - private void setDrawables(LinearLayout container, LoadedIcons loadedIcons) { - // Rearrange child views until we have ImageViews... - LayoutInflater inflater = LayoutInflater.from(getContext()); - int numImages = loadedIcons.icons.size(); - int numImageViews = getChildCount(container, ImageView.class); - if (numImages > numImageViews) { - for (int i = 0; i < numImages - numImageViews; i++) { - ImageView imageView = (ImageView) inflater.inflate( - R.layout.preference_circular_icons_item, container, false); - container.addView(imageView, 0); - } - } else if (numImageViews > numImages) { - for (int i = 0; i < numImageViews - numImages; i++) { - container.removeViewAt(0); - } - } - // ... plus 0/1 TextViews at the end. - if (loadedIcons.extraItems > 0 && !(getLastChild(container) instanceof TextView)) { - TextView plusView = (TextView) inflater.inflate( - R.layout.preference_circular_icons_plus_item, container, false); - container.addView(plusView); - } else if (loadedIcons.extraItems == 0 && (getLastChild(container) instanceof TextView)) { - container.removeViewAt(container.getChildCount() - 1); - } - - // Show images (and +n if needed). - for (int i = 0; i < numImages; i++) { - ImageView imageView = (ImageView) container.getChildAt(i); - imageView.setImageDrawable(loadedIcons.icons.get(i)); - } - if (loadedIcons.extraItems > 0) { - TextView textView = (TextView) checkNotNull(getLastChild(container)); - textView.setText(getContext().getString(R.string.zen_mode_plus_n_items, - loadedIcons.extraItems)); - } - - // Apply enabled/disabled style. - for (int i = 0; i < container.getChildCount(); i++) { - View child = container.getChildAt(i); - child.setAlpha(isEnabled() ? 1.0f : DISABLED_ITEM_ALPHA); - } - } - - private static int getChildCount(ViewGroup parent, Class childClass) { - int count = 0; - for (int i = 0; i < parent.getChildCount(); i++) { - if (childClass.isInstance(parent.getChildAt(i))) { - count++; - } - } - return count; - } - - @Nullable - private static View getLastChild(ViewGroup parent) { - if (parent.getChildCount() == 0) { - return null; - } - return parent.getChildAt(parent.getChildCount() - 1); - } - - @VisibleForTesting(otherwise = VisibleForTesting.NONE) - @Nullable - LoadedIcons getLoadedIcons() { - return mLoadedIcons; + iconContainer.setVisibility(mIconSet != null && mIconSet.size() == 0 ? GONE : VISIBLE); + iconContainer.setEnabled(isEnabled()); + iconContainer.setIcons(mIconSet); } } diff --git a/src/com/android/settings/notification/modes/CircularIconsView.java b/src/com/android/settings/notification/modes/CircularIconsView.java new file mode 100644 index 00000000000..b0e4280129b --- /dev/null +++ b/src/com/android/settings/notification/modes/CircularIconsView.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2024 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.notification.modes; + +import static com.google.common.base.Preconditions.checkNotNull; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import com.android.settings.R; + +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; + +import java.util.List; +import java.util.concurrent.Executor; + +public class CircularIconsView extends LinearLayout { + + private static final float DISABLED_ITEM_ALPHA = 0.3f; + + record Icons(ImmutableList icons, int extraItems) { } + + private Executor mUiExecutor; + private int mNumberOfCirclesThatFit; + + // Chronologically, fields will be set top-to-bottom. + @Nullable private CircularIconSet mIconSet; + @Nullable private ListenableFuture> mPendingLoadIconsFuture; + @Nullable private Icons mDisplayedIcons; + + public CircularIconsView(Context context) { + super(context); + setUiExecutor(context.getMainExecutor()); + } + + public CircularIconsView(Context context, AttributeSet attrs) { + super(context, attrs); + setUiExecutor(context.getMainExecutor()); + } + + public CircularIconsView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setUiExecutor(context.getMainExecutor()); + } + + public CircularIconsView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + setUiExecutor(context.getMainExecutor()); + } + + @VisibleForTesting + void setUiExecutor(Executor uiExecutor) { + mUiExecutor = uiExecutor; + } + + void setIcons(CircularIconSet iconSet) { + if (mIconSet != null && mIconSet.equals(iconSet)) { + return; + } + + mIconSet = checkNotNull(iconSet); + cancelPendingTasks(); + if (getMeasuredWidth() != 0) { + startLoadingIcons(iconSet); + } + } + + private void cancelPendingTasks() { + mDisplayedIcons = null; + if (mPendingLoadIconsFuture != null) { + mPendingLoadIconsFuture.cancel(true); + mPendingLoadIconsFuture = null; + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + int numFitting = getNumberOfCirclesThatFit(); + if (mNumberOfCirclesThatFit != numFitting) { + // View has been measured for the first time OR its dimensions have changed since then. + // Keep track, because we want to reload stuff if more (or less) items fit. + mNumberOfCirclesThatFit = numFitting; + + if (mIconSet != null) { + cancelPendingTasks(); + startLoadingIcons(mIconSet); + } + } + } + + private int getNumberOfCirclesThatFit() { + Resources res = getContext().getResources(); + int availableSpace = getMeasuredWidth(); + int iconHorizontalSpace = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter) + + res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_margin_between); + return availableSpace / iconHorizontalSpace; + } + + private void startLoadingIcons(CircularIconSet iconSet) { + int numCirclesThatFit = getNumberOfCirclesThatFit(); + + List> iconFutures; + int extraItems; + if (iconSet.size() > numCirclesThatFit) { + // Reserve one space for the (+xx) textview. + int numIconsToShow = numCirclesThatFit - 1; + if (numIconsToShow < 0) { + numIconsToShow = 0; + } + iconFutures = iconSet.getIcons(numIconsToShow); + extraItems = iconSet.size() - numIconsToShow; + } else { + // Fit exactly or with remaining space. + iconFutures = iconSet.getIcons(); + extraItems = 0; + } + + // Display icons when all are ready (more consistent than randomly loading). + mPendingLoadIconsFuture = Futures.allAsList(iconFutures); + FutureUtil.whenDone( + mPendingLoadIconsFuture, + icons -> setDrawables(new Icons(ImmutableList.copyOf(icons), extraItems)), + mUiExecutor); + } + + private void setDrawables(Icons icons) { + mDisplayedIcons = icons; + + // Rearrange child views until we have ImageViews... + LayoutInflater inflater = LayoutInflater.from(getContext()); + int numImages = icons.icons.size(); + int numImageViews = getChildCount(ImageView.class); + if (numImages > numImageViews) { + for (int i = 0; i < numImages - numImageViews; i++) { + ImageView imageView = (ImageView) inflater.inflate( + R.layout.preference_circular_icons_item, this, false); + addView(imageView, 0); + } + } else if (numImageViews > numImages) { + for (int i = 0; i < numImageViews - numImages; i++) { + removeViewAt(0); + } + } + // ... plus 0/1 TextViews at the end. + if (icons.extraItems > 0 && !(getLastChild() instanceof TextView)) { + TextView plusView = (TextView) inflater.inflate( + R.layout.preference_circular_icons_plus_item, this, false); + this.addView(plusView); + } else if (icons.extraItems == 0 && (getLastChild() instanceof TextView)) { + removeViewAt(getChildCount() - 1); + } + + // Show images (and +n if needed). + for (int i = 0; i < numImages; i++) { + ImageView imageView = (ImageView) getChildAt(i); + imageView.setImageDrawable(icons.icons.get(i)); + } + if (icons.extraItems > 0) { + TextView textView = (TextView) checkNotNull(getLastChild()); + textView.setText(getContext().getString(R.string.zen_mode_plus_n_items, + icons.extraItems)); + } + + applyEnabledDisabledAppearance(isEnabled()); + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + applyEnabledDisabledAppearance(isEnabled()); + } + + private void applyEnabledDisabledAppearance(boolean enabled) { + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + child.setAlpha(enabled ? 1.0f : DISABLED_ITEM_ALPHA); + } + } + + private int getChildCount(Class childClass) { + int count = 0; + for (int i = 0; i < getChildCount(); i++) { + if (childClass.isInstance(getChildAt(i))) { + count++; + } + } + return count; + } + + @Nullable + private View getLastChild() { + if (getChildCount() == 0) { + return null; + } + return getChildAt(getChildCount() - 1); + } + + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + @Nullable + Icons getDisplayedIcons() { + return mDisplayedIcons; + } +} diff --git a/src/com/android/settings/notification/modes/IconUtil.java b/src/com/android/settings/notification/modes/IconUtil.java index dc4d875869e..33d0d961698 100644 --- a/src/com/android/settings/notification/modes/IconUtil.java +++ b/src/com/android/settings/notification/modes/IconUtil.java @@ -79,6 +79,7 @@ class IconUtil { @Px int innerSizePx = res.getDimensionPixelSize(R.dimen.zen_mode_header_inner_icon_size); Drawable base = composeIcons( + context.getResources(), background, Utils.getColorAttr(context, com.android.internal.R.attr.materialColorSecondaryContainer), @@ -89,6 +90,7 @@ class IconUtil { innerSizePx); Drawable selected = composeIcons( + context.getResources(), background, Utils.getColorAttr(context, com.android.internal.R.attr.materialColorPrimary), outerSizePx, @@ -111,6 +113,7 @@ class IconUtil { */ static Drawable makeIconPickerHeader(@NonNull Context context, Drawable icon) { return composeIconCircle( + context.getResources(), Utils.getColorAttr(context, com.android.internal.R.attr.materialColorSecondaryContainer), context.getResources().getDimensionPixelSize( @@ -129,6 +132,7 @@ class IconUtil { */ static Drawable makeIconPickerItem(@NonNull Context context, @DrawableRes int iconResId) { return composeIconCircle( + context.getResources(), context.getColorStateList(R.color.modes_icon_selectable_background), context.getResources().getDimensionPixelSize( R.dimen.zen_mode_icon_list_item_circle_diameter), @@ -146,6 +150,7 @@ class IconUtil { static Drawable makeCircularIconPreferenceItem(@NonNull Context context, @DrawableRes int iconResId) { return composeIconCircle( + context.getResources(), Utils.getColorAttr(context, com.android.internal.R.attr.materialColorSecondaryContainer), context.getResources().getDimensionPixelSize( @@ -166,6 +171,7 @@ class IconUtil { Resources res = context.getResources(); if (Strings.isNullOrEmpty(displayName)) { return composeIconCircle( + context.getResources(), Utils.getColorAttr(context, com.android.internal.R.attr.materialColorTertiaryContainer), res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter), @@ -204,17 +210,17 @@ class IconUtil { return new BitmapDrawable(context.getResources(), bitmap); } - private static Drawable composeIconCircle(ColorStateList circleColor, @Px int circleDiameterPx, - Drawable icon, ColorStateList iconColor, @Px int iconSizePx) { - return composeIcons(new ShapeDrawable(new OvalShape()), circleColor, circleDiameterPx, icon, - iconColor, iconSizePx); + private static Drawable composeIconCircle(Resources res, ColorStateList circleColor, + @Px int circleDiameterPx, Drawable icon, ColorStateList iconColor, @Px int iconSizePx) { + return composeIcons(res, new ShapeDrawable(new OvalShape()), circleColor, circleDiameterPx, + icon, iconColor, iconSizePx); } - private static Drawable composeIcons(Drawable outer, ColorStateList outerColor, + private static Drawable composeIcons(Resources res, Drawable outer, ColorStateList outerColor, @Px int outerSizePx, Drawable icon, ColorStateList iconColor, @Px int iconSizePx) { - Drawable background = checkNotNull(outer.getConstantState()).newDrawable().mutate(); + Drawable background = checkNotNull(outer.getConstantState()).newDrawable(res).mutate(); background.setTintList(outerColor); - Drawable foreground = checkNotNull(icon.getConstantState()).newDrawable().mutate(); + Drawable foreground = checkNotNull(icon.getConstantState()).newDrawable(res).mutate(); foreground.setTintList(iconColor); LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] { background, foreground }); diff --git a/src/com/android/settings/notification/modes/SetupInterstitialActivity.java b/src/com/android/settings/notification/modes/SetupInterstitialActivity.java index 830baaf7bfa..84086245b53 100644 --- a/src/com/android/settings/notification/modes/SetupInterstitialActivity.java +++ b/src/com/android/settings/notification/modes/SetupInterstitialActivity.java @@ -21,9 +21,13 @@ import static android.app.AutomaticZenRule.TYPE_DRIVING; import static android.app.AutomaticZenRule.TYPE_IMMERSIVE; import static android.app.AutomaticZenRule.TYPE_MANAGED; import static android.app.AutomaticZenRule.TYPE_OTHER; +import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR; +import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME; import static android.app.AutomaticZenRule.TYPE_THEATER; +import static android.app.AutomaticZenRule.TYPE_UNKNOWN; import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID; +import android.annotation.SuppressLint; import android.app.ActionBar; import android.app.settings.SettingsEnums; import android.content.Context; @@ -39,6 +43,7 @@ import android.widget.Toolbar; import androidx.activity.EdgeToEdge; import androidx.annotation.NonNull; +import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; import androidx.fragment.app.FragmentActivity; @@ -124,6 +129,11 @@ public class SetupInterstitialActivity extends FragmentActivity { title.setText(mode.getName()); } + TextView subtitle = findViewById(R.id.mode_name_subtitle); + if (subtitle != null) { + subtitle.setText(getSubtitle(mode)); + } + ImageView img = findViewById(R.id.image); if (img != null) { setImage(img, mode); @@ -135,6 +145,27 @@ public class SetupInterstitialActivity extends FragmentActivity { } } + @StringRes + @SuppressLint("SwitchIntDef") + private static int getSubtitle(ZenMode mode) { + if (mode.isSystemOwned()) { + return switch (mode.getType()) { + case TYPE_SCHEDULE_TIME -> R.string.zen_mode_inspiration_schedule_time; + case TYPE_SCHEDULE_CALENDAR -> R.string.zen_mode_inspiration_schedule_calendar; + default -> R.string.zen_mode_inspiration_generic; // Custom Manual + }; + } else { + return switch (mode.getType()) { + case TYPE_BEDTIME -> R.string.zen_mode_inspiration_bedtime; + case TYPE_DRIVING -> R.string.zen_mode_inspiration_driving; + case TYPE_IMMERSIVE -> R.string.zen_mode_inspiration_immersive; + case TYPE_THEATER -> R.string.zen_mode_inspiration_theater; + case TYPE_MANAGED -> R.string.zen_mode_inspiration_managed; + default -> R.string.zen_mode_inspiration_generic; // Including OTHER, UNKNOWN. + }; + } + } + private void setImage(@NonNull ImageView img, @NonNull ZenMode mode) { int drawableRes = switch (mode.getType()) { case TYPE_BEDTIME -> R.drawable.modes_interstitial_bedtime; @@ -142,7 +173,9 @@ public class SetupInterstitialActivity extends FragmentActivity { case TYPE_IMMERSIVE -> R.drawable.modes_interstitial_immersive; case TYPE_THEATER -> R.drawable.modes_interstitial_theater; case TYPE_MANAGED -> R.drawable.modes_interstitial_managed; - case TYPE_OTHER -> R.drawable.modes_interstitial_other; + case TYPE_OTHER, TYPE_SCHEDULE_CALENDAR, TYPE_SCHEDULE_TIME -> + R.drawable.modes_interstitial_other; + case TYPE_UNKNOWN -> R.drawable.modes_interstitial_unknown; default -> R.drawable.modes_interstitial_unknown; }; diff --git a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java index 45287abdc61..7b17f0c77e6 100644 --- a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java @@ -109,7 +109,7 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr if (zenMode.getPolicy().getAllowedChannels() == ZenPolicy.CHANNEL_POLICY_NONE) { mPreference.setSummary(R.string.zen_mode_apps_none_apps); - mPreference.displayIcons(CircularIconSet.EMPTY); + mPreference.setIcons(CircularIconSet.EMPTY); if (mAppSession != null) { mAppSession.deactivateSession(); } @@ -151,7 +151,7 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr ImmutableList apps = getAppsBypassingDndSortedByName(allApps); mPreference.setSummary(mSummaryHelper.getAppsSummary(mZenMode, apps)); - mPreference.displayIcons(new CircularIconSet<>(apps, + mPreference.setIcons(new CircularIconSet<>(apps, app -> mAppIconRetriever.apply(app.info)), APP_ENTRY_EQUIVALENCE); } diff --git a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java index 5b26364c938..9613d98eb1e 100644 --- a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java @@ -72,7 +72,7 @@ class ZenModeOtherLinkPreferenceController extends AbstractZenModePreferenceCont preference.setEnabled(zenMode.isEnabled()); preference.setSummary(mSummaryHelper.getOtherSoundCategoriesSummary(zenMode)); - ((CircularIconsPreference) preference).displayIcons(getSoundIcons(zenMode.getPolicy())); + ((CircularIconsPreference) preference).setIcons(getSoundIcons(zenMode.getPolicy())); } private CircularIconSet getSoundIcons(ZenPolicy policy) { diff --git a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java index 9aad4603588..bf554711637 100644 --- a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java @@ -96,7 +96,7 @@ class ZenModePeopleLinkPreferenceController extends AbstractZenModePreferenceCon preference.setEnabled(zenMode.isEnabled()); preference.setSummary(mSummaryHelper.getPeopleSummary(zenMode.getPolicy())); - ((CircularIconsPreference) preference).displayIcons(getPeopleIcons(zenMode.getPolicy()), + ((CircularIconsPreference) preference).setIcons(getPeopleIcons(zenMode.getPolicy()), PEOPLE_ITEM_EQUIVALENCE); } diff --git a/src/com/android/settings/wifi/repository/WifiHotspotRepository.java b/src/com/android/settings/wifi/repository/WifiHotspotRepository.java index e523831d229..4dc2e9e52f3 100644 --- a/src/com/android/settings/wifi/repository/WifiHotspotRepository.java +++ b/src/com/android/settings/wifi/repository/WifiHotspotRepository.java @@ -397,7 +397,7 @@ public class WifiHotspotRepository { * @return {@code true} if Wi-Fi Hotspot 5 GHz Band is available */ public boolean is5gAvailable() { - if (!mBand5g.isUsableChannelsReady && is5GHzBandSupported()) { + if (!mBand5g.isChannelsReady && is5GHzBandSupported()) { isChannelAvailable(mBand5g); } return mBand5g.isAvailable(); @@ -439,7 +439,7 @@ public class WifiHotspotRepository { * @return {@code true} if Wi-Fi Hotspot 6 GHz Band is available */ public boolean is6gAvailable() { - if (!mBand6g.isUsableChannelsReady && is6GHzBandSupported()) { + if (!mBand6g.isChannelsReady && is6GHzBandSupported()) { isChannelAvailable(mBand6g); } return mBand6g.isAvailable(); @@ -475,19 +475,19 @@ public class WifiHotspotRepository { List channels = mWifiManager.getAllowedChannels(sapBand.band, OP_MODE_SAP); log("isChannelAvailable(), band:" + sapBand.band + ", channels:" + channels); - sapBand.hasUsableChannels = (channels != null && channels.size() > 0); - sapBand.isUsableChannelsUnsupported = false; + sapBand.hasChannels = (channels != null && channels.size() > 0); + sapBand.isChannelsUnsupported = false; } catch (IllegalArgumentException e) { - Log.e(TAG, "Querying usable SAP channels failed, band:" + sapBand.band); - sapBand.hasUsableChannels = false; - sapBand.isUsableChannelsUnsupported = true; + Log.e(TAG, "Querying SAP channels failed, band:" + sapBand.band); + sapBand.hasChannels = false; + sapBand.isChannelsUnsupported = true; } catch (UnsupportedOperationException e) { // This is expected on some hardware. - Log.e(TAG, "Querying usable SAP channels is unsupported, band:" + sapBand.band); - sapBand.hasUsableChannels = false; - sapBand.isUsableChannelsUnsupported = true; + Log.e(TAG, "Querying SAP channels is unsupported, band:" + sapBand.band); + sapBand.hasChannels = false; + sapBand.isChannelsUnsupported = true; } - sapBand.isUsableChannelsReady = true; + sapBand.isChannelsReady = true; log("isChannelAvailable(), " + sapBand); return sapBand.isAvailable(); } @@ -531,8 +531,8 @@ public class WifiHotspotRepository { } protected void purgeRefreshData() { - mBand5g.isUsableChannelsReady = false; - mBand6g.isUsableChannelsReady = false; + mBand5g.isChannelsReady = false; + mBand6g.isChannelsReady = false; } protected void startAutoRefresh() { @@ -615,15 +615,15 @@ public class WifiHotspotRepository { @VisibleForTesting void updateCapabilityChanged() { - if (mBand5g.isUsableChannelsUnsupported) { + if (mBand5g.isChannelsUnsupported) { update5gAvailable(); log("updateCapabilityChanged(), " + mBand5g); } - if (mBand6g.isUsableChannelsUnsupported) { + if (mBand6g.isChannelsUnsupported) { update6gAvailable(); log("updateCapabilityChanged(), " + mBand6g); } - if (mBand5g.isUsableChannelsUnsupported || mBand6g.isUsableChannelsUnsupported) { + if (mBand5g.isChannelsUnsupported || mBand6g.isChannelsUnsupported) { updateSpeedType(); } } @@ -676,9 +676,9 @@ public class WifiHotspotRepository { @VisibleForTesting static class SapBand { public int band; - public boolean isUsableChannelsReady; - public boolean hasUsableChannels; - public boolean isUsableChannelsUnsupported; + public boolean isChannelsReady; + public boolean hasChannels; + public boolean isChannelsUnsupported; public boolean hasCapability; SapBand(int band) { @@ -689,7 +689,7 @@ public class WifiHotspotRepository { * Return whether SoftAp band is available or not. */ public boolean isAvailable() { - return isUsableChannelsUnsupported ? hasCapability : hasUsableChannels; + return isChannelsUnsupported ? hasCapability : hasChannels; } @Override @@ -697,10 +697,10 @@ public class WifiHotspotRepository { public String toString() { return "SapBand{" + "band:" + band - + ",isUsableChannelsReady:" + isUsableChannelsReady - + ",hasUsableChannels:" + hasUsableChannels - + ",isUsableChannelsUnsupported:" + isUsableChannelsUnsupported - + ",hasChannelsCapability:" + hasCapability + + ",isChannelsReady:" + isChannelsReady + + ",hasChannels:" + hasChannels + + ",isChannelsUnsupported:" + isChannelsUnsupported + + ",hasCapability:" + hasCapability + '}'; } } diff --git a/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java index 5fd11f910fa..67970613822 100644 --- a/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java @@ -58,8 +58,6 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { private ContentResolver mContentResolver; private DaltonizerSaturationSeekbarPreferenceController mController; - private int mOriginalSaturationLevel = -1; - private PreferenceScreen mScreen; private LifecycleOwner mLifecycleOwner; private Lifecycle mLifecycle; @@ -73,10 +71,6 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { public void setup() { Context context = ApplicationProvider.getApplicationContext(); mContentResolver = context.getContentResolver(); - mOriginalSaturationLevel = Settings.Secure.getInt( - mContentResolver, - Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, - 7); mPreference = new SeekBarPreference(context); mPreference.setKey(ToggleDaltonizerPreferenceFragment.KEY_SATURATION); @@ -92,10 +86,18 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { @After public void cleanup() { - Settings.Secure.putInt( + Settings.Secure.putString( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, + null); + Settings.Secure.putString( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, + null); + Settings.Secure.putString( mContentResolver, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, - mOriginalSaturationLevel); + null); } @Test @@ -111,6 +113,22 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE); } + @Test + @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) + public void getAvailabilityStatus_defaultSettings_unavailable() { + // By default enabled == false. + assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) + public void getAvailabilityStatus_enabledDefaultDisplayMode_available() { + setDaltonizerEnabled(1); + + // By default display mode is deuteranomaly. + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + @Test @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) public void getAvailabilityStatus_flagEnabledProtanEnabled_available() { @@ -306,10 +324,7 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { mLifecycle.addObserver(mController); mLifecycle.handleLifecycleEvent(ON_RESUME); - Settings.Secure.putInt( - mContentResolver, - Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, - 1); + setDaltonizerEnabled(1); shadowOf(Looper.getMainLooper()).idle(); assertThat(mPreference.isEnabled()).isTrue(); @@ -324,10 +339,7 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { mLifecycle.addObserver(mController); mLifecycle.handleLifecycleEvent(ON_RESUME); - Settings.Secure.putInt( - mContentResolver, - Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, - 0); + setDaltonizerEnabled(0); shadowOf(Looper.getMainLooper()).idle(); assertThat(mPreference.isEnabled()).isFalse(); @@ -342,10 +354,7 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { mLifecycle.addObserver(mController); mLifecycle.handleLifecycleEvent(ON_RESUME); - Settings.Secure.putInt( - mContentResolver, - Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, - 0); + setDaltonizerDisplay(0); shadowOf(Looper.getMainLooper()).idle(); assertThat(mPreference.isEnabled()).isFalse(); @@ -361,23 +370,28 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { mLifecycle.handleLifecycleEvent(ON_STOP); // enabled. - Settings.Secure.putInt( - mContentResolver, - Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, - 1); + setDaltonizerEnabled(1); shadowOf(Looper.getMainLooper()).idle(); assertThat(mPreference.isEnabled()).isFalse(); } private void setDaltonizerMode(int enabled, int mode) { + setDaltonizerEnabled(enabled); + setDaltonizerDisplay(mode); + } + + private void setDaltonizerEnabled(int enabled) { Settings.Secure.putInt( mContentResolver, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, enabled); - Settings.Secure.putInt( + } + + private void setDaltonizerDisplay(int mode) { + Settings.Secure.putString( mContentResolver, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, - mode); + Integer.toString(mode)); } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java index 4c648083106..85fc6e210ce 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java @@ -29,6 +29,7 @@ import android.content.ContentValues; import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.Drawable; +import android.os.Bundle; import android.os.LocaleList; import android.text.format.DateUtils; @@ -57,6 +58,7 @@ public final class BatteryUsageBreakdownControllerTest { private static final String PREF_KEY = "pref_key"; private static final String PREF_KEY2 = "pref_key2"; private static final String PREF_SUMMARY = "fake preference summary"; + private static final String KEY_SPINNER_POSITION = "spinner_position"; private static final long TIME_LESS_THAN_HALF_MINUTE = DateUtils.MINUTE_IN_MILLIS / 2 - 1; @Mock private InstrumentedPreferenceFragment mFragment; @@ -148,6 +150,15 @@ public final class BatteryUsageBreakdownControllerTest { verify(mAppListPreferenceGroup).removeAll(); } + @Test + public void onSaveInstanceState_returnExpectedResult() { + mBatteryUsageBreakdownController.mSpinnerPosition = 1; + final Bundle savedInstanceState = new Bundle(); + mBatteryUsageBreakdownController.onSaveInstanceState(savedInstanceState); + + assertThat(savedInstanceState.getInt(KEY_SPINNER_POSITION)).isEqualTo(1); + } + @Test public void addAllPreferences_addAllPreferences() { final String appLabel = "fake app label"; diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/SpinnerPreferenceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/SpinnerPreferenceTest.java deleted file mode 100644 index 8050984938e..00000000000 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/SpinnerPreferenceTest.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2022 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.batteryusage; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; - -import android.content.Context; -import android.widget.Spinner; - -import androidx.preference.Preference; - -import com.android.settings.R; - -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.RuntimeEnvironment; - -@RunWith(RobolectricTestRunner.class) -public final class SpinnerPreferenceTest { - - private Context mContext; - private SpinnerPreference mSpinnerPreference; - - @Mock private Spinner mMockSpinner; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mContext = spy(RuntimeEnvironment.application); - mSpinnerPreference = new SpinnerPreference(mContext, /* attrs= */ null); - } - - @Test - public void constructor_returnExpectedResult() { - assertThat(mSpinnerPreference.getLayoutResource()).isEqualTo(R.layout.preference_spinner); - } - - @Test - public void initializeSpinner_returnExpectedResult() { - final String[] items = new String[] {"item1", "item2"}; - mSpinnerPreference.initializeSpinner(items, null); - assertThat(mSpinnerPreference.mItems).isEqualTo(items); - } - - @Test - public void onSaveInstanceState_returnExpectedResult() { - doReturn(1).when(mMockSpinner).getSelectedItemPosition(); - mSpinnerPreference.mSpinner = mMockSpinner; - SpinnerPreference.SavedState savedState = - (SpinnerPreference.SavedState) mSpinnerPreference.onSaveInstanceState(); - assertThat(savedState.getSpinnerPosition()).isEqualTo(1); - } - - @Test - public void onRestoreInstanceState_returnExpectedResult() { - SpinnerPreference.SavedState savedState = - new SpinnerPreference.SavedState(Preference.BaseSavedState.EMPTY_STATE, 2); - mSpinnerPreference.onRestoreInstanceState(savedState); - assertThat(mSpinnerPreference.mSavedSpinnerPosition).isEqualTo(2); - } -} diff --git a/tests/robotests/src/com/android/settings/notification/modes/CircularIconsPreferenceTest.java b/tests/robotests/src/com/android/settings/notification/modes/CircularIconsPreferenceTest.java index d145f255c5b..55448329d6f 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/CircularIconsPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/CircularIconsPreferenceTest.java @@ -62,8 +62,7 @@ public class CircularIconsPreferenceTest { private Context mContext; private CircularIconsPreference mPreference; - private PreferenceViewHolder mViewHolder; - private ViewGroup mContainer; + private CircularIconsView mContainer; private int mOneIconWidth; @@ -73,179 +72,211 @@ public class CircularIconsPreferenceTest { mContext = RuntimeEnvironment.application; CircularIconSet.sExecutorService = MoreExecutors.newDirectExecutorService(); mPreference = new TestableCircularIconsPreference(mContext); - // Tests should call bindAndMeasureViewHolder() so that icons can be added. + // Tests should call bindAndLayoutViewHolder() so that icons can be added. Resources res = mContext.getResources(); mOneIconWidth = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter) + res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_margin_between); } - private void bindAndMeasureViewHolder(int viewWidth) { + private void bindAndLayoutViewHolder(int viewWidth) { bindViewHolder(); - measureViewHolder(viewWidth); + layoutViewHolder(viewWidth); } private void bindViewHolder() { View preferenceView = LayoutInflater.from(mContext).inflate(mPreference.getLayoutResource(), null); mContainer = checkNotNull(preferenceView.findViewById(R.id.circles_container)); - mViewHolder = PreferenceViewHolder.createInstanceForTests(preferenceView); - mPreference.onBindViewHolder(mViewHolder); + mContainer.setUiExecutor(MoreExecutors.directExecutor()); + PreferenceViewHolder viewHolder = PreferenceViewHolder.createInstanceForTests( + preferenceView); + mPreference.onBindViewHolder(viewHolder); } - private void measureViewHolder(int viewWidth) { + private void layoutViewHolder(int viewWidth) { checkState(mContainer != null, "Call bindViewHolder() first!"); mContainer.measure(makeMeasureSpec(viewWidth, View.MeasureSpec.EXACTLY), makeMeasureSpec(1000, View.MeasureSpec.EXACTLY)); - mContainer.getViewTreeObserver().dispatchOnGlobalLayout(); + mContainer.layout(0, 0, viewWidth, 1000); } @Test - public void displayIcons_loadsIcons() { + public void setIcons_loadsIcons() { CircularIconSet iconSet = new CircularIconSet<>(ImmutableList.of(1, 2), ColorDrawable::new); - bindAndMeasureViewHolder(VIEW_WIDTH); - mPreference.displayIcons(iconSet); + bindAndLayoutViewHolder(VIEW_WIDTH); + mPreference.setIcons(iconSet); - assertThat(getIcons(mContainer)).hasSize(2); - assertThat(((ColorDrawable) getIcons(mContainer).get(0)).getColor()).isEqualTo(1); - assertThat(((ColorDrawable) getIcons(mContainer).get(1)).getColor()).isEqualTo(2); + assertThat(getDrawables(mContainer)).hasSize(2); + assertThat(((ColorDrawable) getDrawables(mContainer).get(0)).getColor()).isEqualTo(1); + assertThat(((ColorDrawable) getDrawables(mContainer).get(1)).getColor()).isEqualTo(2); assertThat(getPlusText(mContainer)).isNull(); } @Test - public void displayIcons_noIcons_hidesRow() { + public void setIcons_noIcons_hidesRow() { CircularIconSet iconSet = new CircularIconSet<>(ImmutableList.of(), ColorDrawable::new); - bindAndMeasureViewHolder(VIEW_WIDTH); - mPreference.displayIcons(iconSet); + bindAndLayoutViewHolder(VIEW_WIDTH); + mPreference.setIcons(iconSet); assertThat(mContainer.getVisibility()).isEqualTo(View.GONE); } @Test - public void displayIcons_exactlyMaxIcons_loadsAllIcons() throws Exception { + public void setIcons_exactlyMaxIcons_loadsAllIcons() throws Exception { int width = 300; int fittingCircles = width / mOneIconWidth; CircularIconSet iconSet = new CircularIconSet<>( IntStream.range(0, fittingCircles).boxed().toList(), ColorDrawable::new); - bindAndMeasureViewHolder(width); - mPreference.displayIcons(iconSet); + bindAndLayoutViewHolder(width); + mPreference.setIcons(iconSet); - assertThat(getIcons(mContainer)).hasSize(fittingCircles); - assertThat(getIcons(mContainer)).containsExactlyElementsIn( + assertThat(getDrawables(mContainer)).hasSize(fittingCircles); + assertThat(getDrawables(mContainer)).containsExactlyElementsIn( Futures.allAsList(iconSet.getIcons()).get()).inOrder(); assertThat(getPlusText(mContainer)).isNull(); - } @Test - public void displayIcons_tooManyIcons_loadsFirstNAndPlusIcon() throws Exception { + public void setIcons_tooManyIcons_loadsFirstNAndPlusIcon() throws Exception { int width = 300; int fittingCircles = width / mOneIconWidth; CircularIconSet iconSet = new CircularIconSet<>( IntStream.range(0, fittingCircles + 5).boxed().toList(), ColorDrawable::new); - bindAndMeasureViewHolder(width); - mPreference.displayIcons(iconSet); + bindAndLayoutViewHolder(width); + mPreference.setIcons(iconSet); // N-1 icons, plus (+6) text. - assertThat(getIcons(mContainer)).hasSize(fittingCircles - 1); - assertThat(getIcons(mContainer)).containsExactlyElementsIn( + assertThat(getDrawables(mContainer)).hasSize(fittingCircles - 1); + assertThat(getDrawables(mContainer)).containsExactlyElementsIn( Futures.allAsList(iconSet.getIcons(fittingCircles - 1)).get()) .inOrder(); assertThat(getPlusText(mContainer)).isEqualTo("+6"); } @Test - public void displayIcons_teenyTinySpace_showsPlusIcon_noCrash() { + public void setIcons_teenyTinySpace_showsPlusIcon_noCrash() { CircularIconSet iconSet = new CircularIconSet<>(ImmutableList.of(1, 2), ColorDrawable::new); - bindAndMeasureViewHolder(1); - mPreference.displayIcons(iconSet); + bindAndLayoutViewHolder(1); + mPreference.setIcons(iconSet); - assertThat(getIcons(mContainer)).isEmpty(); + assertThat(getDrawables(mContainer)).isEmpty(); assertThat(getPlusText(mContainer)).isEqualTo("+2"); } @Test - public void displayIcons_beforeBind_loadsIconsOnBindAndMeasure() { + public void setIcons_beforeBind_loadsIconsOnBindAndMeasure() { CircularIconSet iconSet = new CircularIconSet<>(ImmutableList.of(1, 2, 3), ColorDrawable::new); - mPreference.displayIcons(iconSet); - assertThat(mPreference.getLoadedIcons()).isNull(); // Hold... + mPreference.setIcons(iconSet); + assertThat(mContainer).isNull(); // Hold... bindViewHolder(); - assertThat(mPreference.getLoadedIcons()).isNull(); // Hooooold... + assertThat(getDrawables(mContainer)).hasSize(0); // Hooooold... - measureViewHolder(VIEW_WIDTH); - assertThat(mPreference.getLoadedIcons().icons()).hasSize(3); - assertThat(getIcons(mContainer)).hasSize(3); + layoutViewHolder(VIEW_WIDTH); + assertThat(getDrawables(mContainer)).hasSize(3); } @Test - public void displayIcons_beforeMeasure_loadsIconsOnMeasure() { + public void setIcons_beforeMeasure_loadsIconsOnMeasure() { CircularIconSet iconSet = new CircularIconSet<>(ImmutableList.of(1, 2, 3), ColorDrawable::new); bindViewHolder(); - mPreference.displayIcons(iconSet); - assertThat(mPreference.getLoadedIcons()).isNull(); + mPreference.setIcons(iconSet); + assertThat(getDrawables(mContainer)).hasSize(0); - measureViewHolder(VIEW_WIDTH); - assertThat(getIcons(mContainer)).hasSize(3); + layoutViewHolder(VIEW_WIDTH); + assertThat(getDrawables(mContainer)).hasSize(3); } @Test - public void displayIcons_calledAgain_reloadsIcons() { + public void setIcons_calledAgain_reloadsIcons() { CircularIconSet threeIcons = new CircularIconSet<>(ImmutableList.of(1, 2, 3), ColorDrawable::new); CircularIconSet twoIcons = new CircularIconSet<>(ImmutableList.of(1, 2), ColorDrawable::new); CircularIconSet fourIcons = new CircularIconSet<>(ImmutableList.of(1, 2, 3, 4), ColorDrawable::new); - bindAndMeasureViewHolder(VIEW_WIDTH); + bindAndLayoutViewHolder(VIEW_WIDTH); - mPreference.displayIcons(threeIcons); - assertThat(mPreference.getLoadedIcons()).isNotNull(); - assertThat(getIcons(mContainer)).hasSize(3); + mPreference.setIcons(threeIcons); + assertThat(getDrawables(mContainer)).hasSize(3); - mPreference.displayIcons(twoIcons); - assertThat(mPreference.getLoadedIcons()).isNotNull(); - assertThat(getIcons(mContainer)).hasSize(2); + mPreference.setIcons(twoIcons); + assertThat(getDrawables(mContainer)).hasSize(2); - mPreference.displayIcons(fourIcons); - assertThat(mPreference.getLoadedIcons()).isNotNull(); - assertThat(getIcons(mContainer)).hasSize(4); + mPreference.setIcons(fourIcons); + assertThat(getDrawables(mContainer)).hasSize(4); } @Test - public void displayIcons_sameSet_doesNotReloadIcons() { + public void setIcons_sameSet_doesNotReloadIcons() { CircularIconSet one = new CircularIconSet<>(ImmutableList.of(1, 2, 3), ColorDrawable::new); CircularIconSet same = Mockito.spy(new CircularIconSet<>(ImmutableList.of(1, 2, 3), ColorDrawable::new)); when(same.getIcons()).thenThrow(new RuntimeException("Shouldn't be called!")); - bindAndMeasureViewHolder(VIEW_WIDTH); + bindAndLayoutViewHolder(VIEW_WIDTH); - mPreference.displayIcons(one); + mPreference.setIcons(one); - mPreference.displayIcons(same); // if no exception, wasn't called. + mPreference.setIcons(same); // if no exception, wasn't called. } + @Test + public void sizeChanged_reloadsIconsIfDifferentFit() { + CircularIconSet largeIconSet = new CircularIconSet<>( + IntStream.range(0, 100).boxed().toList(), + ColorDrawable::new); + mPreference.setIcons(largeIconSet); + + // Base space -> some icons + int firstWidth = 600; + int firstFittingCircles = firstWidth / mOneIconWidth; + bindAndLayoutViewHolder(firstWidth); + + assertThat(getDrawables(mContainer)).hasSize(firstFittingCircles - 1); + assertThat(getPlusText(mContainer)).isEqualTo("+" + (100 - (firstFittingCircles - 1))); + + // More space -> more icons + int secondWidth = 1000; + int secondFittingCircles = secondWidth / mOneIconWidth; + assertThat(secondFittingCircles).isGreaterThan(firstFittingCircles); + bindAndLayoutViewHolder(secondWidth); + + assertThat(getDrawables(mContainer)).hasSize(secondFittingCircles - 1); + assertThat(getPlusText(mContainer)).isEqualTo("+" + (100 - (secondFittingCircles - 1))); + + // Less space -> fewer icons + int thirdWidth = 600; + int thirdFittingCircles = thirdWidth / mOneIconWidth; + bindAndLayoutViewHolder(thirdWidth); + + assertThat(getDrawables(mContainer)).hasSize(thirdFittingCircles - 1); + assertThat(getPlusText(mContainer)).isEqualTo("+" + (100 - (thirdFittingCircles - 1))); + } + + @Test public void onBindViewHolder_withDifferentView_reloadsIconsCorrectly() { View preferenceViewOne = LayoutInflater.from(mContext).inflate( mPreference.getLayoutResource(), null); - ViewGroup containerOne = preferenceViewOne.findViewById(R.id.circles_container); + CircularIconsView containerOne = preferenceViewOne.findViewById(R.id.circles_container); + containerOne.setUiExecutor(MoreExecutors.directExecutor()); PreferenceViewHolder viewHolderOne = PreferenceViewHolder.createInstanceForTests( preferenceViewOne); containerOne.measure(makeMeasureSpec(1000, View.MeasureSpec.EXACTLY), @@ -253,7 +284,8 @@ public class CircularIconsPreferenceTest { View preferenceViewTwo = LayoutInflater.from(mContext).inflate( mPreference.getLayoutResource(), null); - ViewGroup containerTwo = preferenceViewTwo.findViewById(R.id.circles_container); + CircularIconsView containerTwo = preferenceViewTwo.findViewById(R.id.circles_container); + containerTwo.setUiExecutor(MoreExecutors.directExecutor()); PreferenceViewHolder viewHolderTwo = PreferenceViewHolder.createInstanceForTests( preferenceViewTwo); containerTwo.measure(makeMeasureSpec(1000, View.MeasureSpec.EXACTLY), @@ -265,25 +297,25 @@ public class CircularIconsPreferenceTest { ColorDrawable::new); mPreference.onBindViewHolder(viewHolderOne); - mPreference.displayIcons(iconSetOne); - assertThat(getIcons(containerOne)).hasSize(3); + mPreference.setIcons(iconSetOne); + assertThat(getDrawables(containerOne)).hasSize(3); mPreference.onBindViewHolder(viewHolderTwo); - assertThat(getIcons(containerTwo)).hasSize(3); + assertThat(getDrawables(containerTwo)).hasSize(3); - mPreference.displayIcons(iconSetTwo); + mPreference.setIcons(iconSetTwo); // The second view is updated and the first view is unaffected. - assertThat(getIcons(containerTwo)).hasSize(2); - assertThat(getIcons(containerOne)).hasSize(3); + assertThat(getDrawables(containerTwo)).hasSize(2); + assertThat(getDrawables(containerOne)).hasSize(3); } @Test - public void setEnabled_afterDisplayIcons_showsEnabledOrDisabledImages() { + public void setEnabled_afterSetIcons_showsEnabledOrDisabledImages() { CircularIconSet iconSet = new CircularIconSet<>(ImmutableList.of(1, 2), ColorDrawable::new); - bindAndMeasureViewHolder(VIEW_WIDTH); - mPreference.displayIcons(iconSet); + bindAndLayoutViewHolder(VIEW_WIDTH); + mPreference.setIcons(iconSet); assertThat(getViews(mContainer)).hasSize(2); mPreference.setEnabled(false); @@ -294,13 +326,13 @@ public class CircularIconsPreferenceTest { } @Test - public void setEnabled_beforeDisplayIcons_showsEnabledOrDisabledImages() { + public void setEnabled_beforeSetIcons_showsEnabledOrDisabledImages() { CircularIconSet iconSet = new CircularIconSet<>(ImmutableList.of(1, 2), ColorDrawable::new); mPreference.setEnabled(false); - bindAndMeasureViewHolder(VIEW_WIDTH); - mPreference.displayIcons(iconSet); + bindAndLayoutViewHolder(VIEW_WIDTH); + mPreference.setIcons(iconSet); assertThat(getViews(mContainer)).hasSize(2); assertThat(getViews(mContainer).get(0).getAlpha()).isLessThan(1f); @@ -314,7 +346,7 @@ public class CircularIconsPreferenceTest { return views; } - private static List getIcons(ViewGroup container) { + private static List getDrawables(ViewGroup container) { ArrayList drawables = new ArrayList<>(); for (int i = 0; i < container.getChildCount(); i++) { if (container.getChildAt(i) instanceof ImageView imageView) { diff --git a/tests/robotests/src/com/android/settings/notification/modes/TestableCircularIconsPreference.java b/tests/robotests/src/com/android/settings/notification/modes/TestableCircularIconsPreference.java index 6fefcacad21..6c1b0597779 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/TestableCircularIconsPreference.java +++ b/tests/robotests/src/com/android/settings/notification/modes/TestableCircularIconsPreference.java @@ -20,14 +20,12 @@ import android.content.Context; import androidx.preference.PreferenceViewHolder; -import com.google.common.util.concurrent.MoreExecutors; - class TestableCircularIconsPreference extends CircularIconsPreference { private PreferenceViewHolder mLastViewHolder; TestableCircularIconsPreference(Context context) { - super(context, MoreExecutors.directExecutor()); + super(context); } @Override diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java index 29e9cf9db07..4148fa3cd33 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java @@ -19,6 +19,7 @@ package com.android.settings.notification.modes; import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID; +import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -52,6 +53,7 @@ import android.view.View; import androidx.fragment.app.Fragment; import androidx.preference.PreferenceViewHolder; +import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.ApplicationsState.AppEntry; @@ -82,6 +84,7 @@ public final class ZenModeAppsLinkPreferenceControllerTest { private ZenModeAppsLinkPreferenceController mController; private CircularIconsPreference mPreference; + private CircularIconsView mIconsView; private Context mContext; @Mock @@ -114,6 +117,8 @@ public final class ZenModeAppsLinkPreferenceControllerTest { // Ensure the preference view is bound & measured (needed to add child ImageViews). View preferenceView = LayoutInflater.from(mContext).inflate(mPreference.getLayoutResource(), null); + mIconsView = checkNotNull(preferenceView.findViewById(R.id.circles_container)); + mIconsView.setUiExecutor(MoreExecutors.directExecutor()); preferenceView.measure(View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY)); PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(preferenceView); @@ -273,7 +278,7 @@ public final class ZenModeAppsLinkPreferenceControllerTest { appEntries.add(createAppEntry("test2", mContext.getUserId())); mController.mAppSessionCallbacks.onRebuildComplete(appEntries); - assertThat(mPreference.getLoadedIcons().icons()).hasSize(2); + assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(2); } @Test @@ -313,7 +318,7 @@ public final class ZenModeAppsLinkPreferenceControllerTest { mController.updateState(mPreference, zenModeWithNone); - assertThat(mPreference.getLoadedIcons().icons()).hasSize(0); + assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(0); verifyNoMoreInteractions(mApplicationsState); verifyNoMoreInteractions(mSession); @@ -322,7 +327,7 @@ public final class ZenModeAppsLinkPreferenceControllerTest { verify(mApplicationsState).newSession(any(), any()); verify(mSession).rebuild(any(), any(), anyBoolean()); mController.mAppSessionCallbacks.onRebuildComplete(appEntries); - assertThat(mPreference.getLoadedIcons().icons()).hasSize(1); + assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(1); } @Test @@ -343,11 +348,11 @@ public final class ZenModeAppsLinkPreferenceControllerTest { verify(mApplicationsState).newSession(any(), any()); verify(mSession).rebuild(any(), any(), anyBoolean()); mController.mAppSessionCallbacks.onRebuildComplete(appEntries); - assertThat(mPreference.getLoadedIcons().icons()).hasSize(1); + assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(1); mController.updateState(mPreference, zenModeWithNone); - assertThat(mPreference.getLoadedIcons().icons()).hasSize(0); + assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(0); verify(mSession).deactivateSession(); verifyNoMoreInteractions(mSession); verifyNoMoreInteractions(mApplicationsState); @@ -356,7 +361,7 @@ public final class ZenModeAppsLinkPreferenceControllerTest { // updateState()) is ignored. mController.mAppSessionCallbacks.onRebuildComplete(appEntries); - assertThat(mPreference.getLoadedIcons().icons()).hasSize(0); + assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(0); } @Test diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java index 8aa87e6c903..3db70fa67bc 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java @@ -95,7 +95,7 @@ public final class ZenModeOtherLinkPreferenceControllerTest { mController.updateState(pref, mode); - verify(pref).displayIcons(argThat(iconSet -> iconSet.size() == 3)); + verify(pref).setIcons(argThat(iconSet -> iconSet.size() == 3)); } @Test @@ -107,7 +107,7 @@ public final class ZenModeOtherLinkPreferenceControllerTest { mController.updateState(pref, mode); - verify(pref).displayIcons(argThat(iconSet -> + verify(pref).setIcons(argThat(iconSet -> iconSet.size() == ZenModeSummaryHelper.OTHER_SOUND_CATEGORIES.size())); } } \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java index a4d141e9ca3..8555d710fa0 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java @@ -22,6 +22,7 @@ import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS; import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE; import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED; +import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -50,6 +51,7 @@ import android.view.View; import androidx.preference.PreferenceViewHolder; +import com.android.settings.R; import com.android.settings.notification.modes.ZenHelperBackend.Contact; import com.android.settingslib.notification.ConversationIconFactory; import com.android.settingslib.notification.modes.TestModeBuilder; @@ -76,6 +78,7 @@ public final class ZenModePeopleLinkPreferenceControllerTest { private ZenModePeopleLinkPreferenceController mController; private CircularIconsPreference mPreference; + private CircularIconsView mIconsView; @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -94,6 +97,8 @@ public final class ZenModePeopleLinkPreferenceControllerTest { // Ensure the preference view is bound & measured (needed to add icons). View preferenceView = LayoutInflater.from(mContext).inflate(mPreference.getLayoutResource(), null); + mIconsView = checkNotNull(preferenceView.findViewById(R.id.circles_container)); + mIconsView.setUiExecutor(MoreExecutors.directExecutor()); preferenceView.measure(View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY)); PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(preferenceView); @@ -142,9 +147,9 @@ public final class ZenModePeopleLinkPreferenceControllerTest { mController.updateState(mPreference, mode); - assertThat(mPreference.getLoadedIcons()).isNotNull(); - assertThat(mPreference.getLoadedIcons().icons()).hasSize(2); - assertThat(mPreference.getLoadedIcons().icons().stream() + assertThat(mIconsView.getDisplayedIcons()).isNotNull(); + assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(2); + assertThat(mIconsView.getDisplayedIcons().icons().stream() .map(ColorDrawable.class::cast) .map(d -> d.getColor()).toList()) .containsExactly(2, 3).inOrder(); @@ -162,9 +167,9 @@ public final class ZenModePeopleLinkPreferenceControllerTest { mController.updateState(mPreference, mode); - assertThat(mPreference.getLoadedIcons()).isNotNull(); - assertThat(mPreference.getLoadedIcons().icons()).hasSize(4); - assertThat(mPreference.getLoadedIcons().icons().stream() + assertThat(mIconsView.getDisplayedIcons()).isNotNull(); + assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(4); + assertThat(mIconsView.getDisplayedIcons().icons().stream() .map(ColorDrawable.class::cast) .map(d -> d.getColor()).toList()) .containsExactly(1, 2, 3, 4).inOrder(); @@ -182,8 +187,8 @@ public final class ZenModePeopleLinkPreferenceControllerTest { mController.updateState(mPreference, mode); - assertThat(mPreference.getLoadedIcons()).isNotNull(); - assertThat(mPreference.getLoadedIcons().icons()).hasSize(1); + assertThat(mIconsView.getDisplayedIcons()).isNotNull(); + assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(1); verify(mHelperBackend, never()).getContactPhoto(any()); } @@ -201,8 +206,8 @@ public final class ZenModePeopleLinkPreferenceControllerTest { mController.updateState(mPreference, mode); - assertThat(mPreference.getLoadedIcons()).isNotNull(); - assertThat(mPreference.getLoadedIcons().icons()).hasSize(3); + assertThat(mIconsView.getDisplayedIcons()).isNotNull(); + assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(3); verify(mConversationIconFactory, times(3)).getConversationDrawable((ShortcutInfo) any(), any(), anyInt(), anyBoolean()); } diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceControllerTest.java index 27df89046f4..2569ca3592f 100644 --- a/tests/robotests/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceControllerTest.java @@ -162,7 +162,4 @@ public class ZenModeAddBypassingAppsPreferenceControllerTest { assertThat(pref.getKey()).isEqualTo( ZenModeAddBypassingAppsPreferenceController.KEY_NO_APPS); } - - // TODO(b/331624810): Add tests to verify updateAppList() when the filter is - // ApplicationsState.FILTER_ENABLED_NOT_QUIET } diff --git a/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt b/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt index 19433f3cb19..e7fc3ed3d66 100644 --- a/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt +++ b/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt @@ -111,9 +111,10 @@ class Injector(step: FingerprintNavigationStep.UiStep) { var fingerprintEnrollViewModel = FingerprintEnrollViewModel( - fingerprintManagerInteractor, gatekeeperViewModel, navigationViewModel, + fingerprintManagerInteractor, + fingerprintManagerInteractor, ) var fingerprintEnrollEnrollingViewModel = diff --git a/tests/shared/src/com/android/settings/testutils2/FakeFingerprintManagerInteractor.kt b/tests/shared/src/com/android/settings/testutils2/FakeFingerprintManagerInteractor.kt index 52df724b82d..f61a3d3a02e 100644 --- a/tests/shared/src/com/android/settings/testutils2/FakeFingerprintManagerInteractor.kt +++ b/tests/shared/src/com/android/settings/testutils2/FakeFingerprintManagerInteractor.kt @@ -22,7 +22,14 @@ import android.hardware.biometrics.SensorProperties import android.hardware.fingerprint.FingerprintEnrollOptions import android.hardware.fingerprint.FingerprintSensorProperties import android.hardware.fingerprint.FingerprintSensorPropertiesInternal -import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.AuthenitcateInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrollFingerprintInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrolledFingerprintsInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RemoveFingerprintInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RenameFingerprintInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel @@ -35,7 +42,15 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf /** Fake to be used by other classes to easily fake the FingerprintManager implementation. */ -class FakeFingerprintManagerInteractor : FingerprintManagerInteractor { +class FakeFingerprintManagerInteractor : + AuthenitcateInteractor, + CanEnrollFingerprintsInteractor, + EnrolledFingerprintsInteractor, + EnrollFingerprintInteractor, + GenerateChallengeInteractor, + RemoveFingerprintInteractor, + RenameFingerprintInteractor, + SensorInteractor { var enrollableFingerprints: Int = 5 var enrolledFingerprintsInternal: MutableList = mutableListOf() @@ -67,19 +82,22 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor { override val enrolledFingerprints: Flow> = flow { emit(enrolledFingerprintsInternal) } - override val canEnrollFingerprints: Flow = flow { emit(enrolledFingerprintsInternal.size < enrollableFingerprints) } - override val sensorPropertiesInternal: Flow = flow { emit(sensorProp) } + override fun maxFingerprintsEnrollable(): Int { + return enrollableFingerprints + } - override val maxEnrollableFingerprints: Flow = flow { emit(enrollableFingerprints) } + override val sensorPropertiesInternal: Flow = flow { emit(sensorProp) } + override val hasSideFps: Flow = + flowOf(sensorProp.sensorType == FingerprintSensorType.POWER_BUTTON) override suspend fun enroll( hardwareAuthToken: ByteArray?, enrollReason: EnrollReason, - fingerprintEnrollOptions: FingerprintEnrollOptions + fingerprintEnrollOptions: FingerprintEnrollOptions, ): Flow = flowOf(*enrollStateViewModel.toTypedArray()) override suspend fun removeFingerprint(fp: FingerprintData): Boolean { @@ -92,7 +110,4 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor { } } - override suspend fun hasSideFps(): Boolean { - return sensorProp.sensorType == FingerprintSensorType.POWER_BUTTON - } } 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 67a5957b9f5..691b6112bf7 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 @@ -16,7 +16,6 @@ package com.android.settings.fingerprint2.domain.interactor -import android.content.Context import android.content.Intent import android.hardware.biometrics.ComponentInfoInternal import android.hardware.biometrics.SensorLocationInternal @@ -30,23 +29,37 @@ import android.hardware.fingerprint.FingerprintSensorProperties import android.hardware.fingerprint.FingerprintSensorPropertiesInternal import android.os.CancellationSignal import android.os.Handler -import androidx.test.core.app.ApplicationProvider import com.android.settings.biometrics.GatekeeperPasswordProvider +import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintEnrollmentRepositoryImpl import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository -import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl -import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl -import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor +import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSettingsRepositoryImpl +import com.android.settings.biometrics.fingerprint2.data.repository.UserRepo +import com.android.settings.biometrics.fingerprint2.domain.interactor.AuthenticateInteractorImpl +import com.android.settings.biometrics.fingerprint2.domain.interactor.CanEnrollFingerprintsInteractorImpl +import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollFingerprintInteractorImpl +import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrolledFingerprintsInteractorImpl +import com.android.settings.biometrics.fingerprint2.domain.interactor.GenerateChallengeInteractorImpl +import com.android.settings.biometrics.fingerprint2.domain.interactor.RemoveFingerprintsInteractorImpl +import com.android.settings.biometrics.fingerprint2.domain.interactor.RenameFingerprintsInteractorImpl +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrollFingerprintInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrolledFingerprintsInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RemoveFingerprintInteractor +import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RenameFingerprintInteractor import com.android.settings.biometrics.fingerprint2.lib.model.Default import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData +import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow import com.android.settings.password.ChooseLockSettingsHelper import com.android.systemui.biometrics.shared.model.FingerprintSensor import com.android.systemui.biometrics.shared.model.toFingerprintSensor import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.last import kotlinx.coroutines.launch @@ -75,13 +88,28 @@ import org.mockito.stubbing.OngoingStubbing class FingerprintManagerInteractorTest { @JvmField @Rule var rule = MockitoJUnit.rule() - private lateinit var underTest: FingerprintManagerInteractor - private var context: Context = ApplicationProvider.getApplicationContext() + private lateinit var enrolledFingerprintsInteractorUnderTest: EnrolledFingerprintsInteractor + private lateinit var generateChallengeInteractorUnderTest: GenerateChallengeInteractor + private lateinit var removeFingerprintsInteractorUnderTest: RemoveFingerprintInteractor + private lateinit var renameFingerprintsInteractorUnderTest: RenameFingerprintInteractor + private lateinit var authenticateInteractorImplUnderTest: AuthenticateInteractorImpl + private lateinit var canEnrollFingerprintsInteractorUnderTest: CanEnrollFingerprintsInteractor + private lateinit var enrollInteractorUnderTest: EnrollFingerprintInteractor + + private val userId = 0 private var backgroundDispatcher = StandardTestDispatcher() @Mock private lateinit var fingerprintManager: FingerprintManager @Mock private lateinit var gateKeeperPasswordProvider: GatekeeperPasswordProvider private var testScope = TestScope(backgroundDispatcher) + private var backgroundScope = testScope.backgroundScope + private val flow: FingerprintFlow = Default + private val maxFingerprints = 5 + private val currUser = MutableStateFlow(0) + private val userRepo = + object : UserRepo { + override val currentUser: Flow = currUser + } @Before fun setup() { @@ -89,7 +117,7 @@ class FingerprintManagerInteractorTest { FingerprintSensorPropertiesInternal( 0 /* sensorId */, SensorProperties.STRENGTH_STRONG, - 5 /* maxEnrollmentsPerUser */, + maxFingerprints, listOf(), FingerprintSensorProperties.TYPE_POWER_BUTTON, false /* halControlsIllumination */, @@ -97,20 +125,37 @@ class FingerprintManagerInteractorTest { listOf(SensorLocationInternal.DEFAULT), ) .toFingerprintSensor() + val fingerprintSensorRepository = object : FingerprintSensorRepository { override val fingerprintSensor: Flow = flowOf(sensor) + override val hasSideFps: Flow = flowOf(false) } - underTest = - FingerprintManagerInteractorImpl( - context, - backgroundDispatcher, + val settingsRepository = FingerprintSettingsRepositoryImpl(maxFingerprints) + val fingerprintEnrollmentRepository = + FingerprintEnrollmentRepositoryImpl( fingerprintManager, - fingerprintSensorRepository, - gateKeeperPasswordProvider, - FingerprintEnrollInteractorImpl(context, fingerprintManager, Default), + userRepo, + settingsRepository, + backgroundDispatcher, + backgroundScope, ) + + enrolledFingerprintsInteractorUnderTest = + EnrolledFingerprintsInteractorImpl(fingerprintManager, userId) + generateChallengeInteractorUnderTest = + GenerateChallengeInteractorImpl(fingerprintManager, userId, gateKeeperPasswordProvider) + removeFingerprintsInteractorUnderTest = + RemoveFingerprintsInteractorImpl(fingerprintManager, userId) + renameFingerprintsInteractorUnderTest = + RenameFingerprintsInteractorImpl(fingerprintManager, userId, backgroundDispatcher) + authenticateInteractorImplUnderTest = AuthenticateInteractorImpl(fingerprintManager, userId) + + canEnrollFingerprintsInteractorUnderTest = + CanEnrollFingerprintsInteractorImpl(fingerprintEnrollmentRepository) + + enrollInteractorUnderTest = EnrollFingerprintInteractorImpl(userId, fingerprintManager, flow) } @Test @@ -119,7 +164,8 @@ class FingerprintManagerInteractorTest { whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList()) val emptyFingerprintList: List = emptyList() - assertThat(underTest.enrolledFingerprints.last()).isEqualTo(emptyFingerprintList) + assertThat(enrolledFingerprintsInteractorUnderTest.enrolledFingerprints.last()) + .isEqualTo(emptyFingerprintList) } @Test @@ -129,7 +175,7 @@ class FingerprintManagerInteractorTest { val fingerprintList: List = listOf(expected) whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(fingerprintList) - val list = underTest.enrolledFingerprints.last() + val list = enrolledFingerprintsInteractorUnderTest.enrolledFingerprints.last() assertThat(list!!.size).isEqualTo(fingerprintList.size) val actual = list[0] assertThat(actual.name).isEqualTo(expected.name) @@ -138,24 +184,51 @@ class FingerprintManagerInteractorTest { } @Test - fun testCanEnrollFingerprint() = + fun testCanEnrollFingerprintSucceeds() = testScope.runTest { - val fingerprintList1: List = + val fingerprintList: List = listOf( - Fingerprint("Finger 1,", 2, 3L), - Fingerprint("Finger 2,", 3, 3L), - Fingerprint("Finger 3,", 4, 3L), + Fingerprint("Finger 1", 2, 3L), + Fingerprint("Finger 2", 3, 3L), + Fingerprint("Finger 3", 4, 3L), ) - val fingerprintList2: List = - fingerprintList1.plus( - listOf(Fingerprint("Finger 4,", 5, 3L), Fingerprint("Finger 5,", 6, 3L)) - ) - whenever(fingerprintManager.getEnrolledFingerprints(anyInt())) - .thenReturn(fingerprintList1) - .thenReturn(fingerprintList2) + whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(fingerprintList) - assertThat(underTest.canEnrollFingerprints.last()).isTrue() - assertThat(underTest.canEnrollFingerprints.last()).isFalse() + var result: Boolean? = null + val job = + testScope.launch { + canEnrollFingerprintsInteractorUnderTest.canEnrollFingerprints.collect { result = it } + } + + runCurrent() + job.cancelAndJoin() + + assertThat(result).isTrue() + } + + @Test + fun testCanEnrollFingerprintFails() = + testScope.runTest { + val fingerprintList: List = + listOf( + Fingerprint("Finger 1", 2, 3L), + Fingerprint("Finger 2", 3, 3L), + Fingerprint("Finger 3", 4, 3L), + Fingerprint("Finger 4", 5, 3L), + Fingerprint("Finger 5", 6, 3L), + ) + whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(fingerprintList) + + var result: Boolean? = null + val job = + testScope.launch { + canEnrollFingerprintsInteractorUnderTest.canEnrollFingerprints.collect { result = it } + } + + runCurrent() + job.cancelAndJoin() + + assertThat(result).isFalse() } @Test @@ -178,7 +251,8 @@ class FingerprintManagerInteractorTest { argumentCaptor() var result: Pair? = null - val job = testScope.launch { result = underTest.generateChallenge(1L) } + val job = + testScope.launch { result = generateChallengeInteractorUnderTest.generateChallenge(1L) } runCurrent() verify(fingerprintManager).generateChallenge(anyInt(), capture(generateChallengeCallback)) @@ -201,7 +275,10 @@ class FingerprintManagerInteractorTest { var result: Boolean? = null val job = - testScope.launch { result = underTest.removeFingerprint(fingerprintViewModelToRemove) } + testScope.launch { + result = + removeFingerprintsInteractorUnderTest.removeFingerprint(fingerprintViewModelToRemove) + } runCurrent() verify(fingerprintManager) @@ -224,7 +301,10 @@ class FingerprintManagerInteractorTest { var result: Boolean? = null val job = - testScope.launch { result = underTest.removeFingerprint(fingerprintViewModelToRemove) } + testScope.launch { + result = + removeFingerprintsInteractorUnderTest.removeFingerprint(fingerprintViewModelToRemove) + } runCurrent() verify(fingerprintManager) @@ -246,7 +326,7 @@ class FingerprintManagerInteractorTest { testScope.runTest { val fingerprintToRename = FingerprintData("Finger 2", 1, 2L) - underTest.renameFingerprint(fingerprintToRename, "Woo") + renameFingerprintsInteractorUnderTest.renameFingerprint(fingerprintToRename, "Woo") verify(fingerprintManager).rename(eq(fingerprintToRename.fingerId), anyInt(), safeEq("Woo")) } @@ -257,7 +337,7 @@ class FingerprintManagerInteractorTest { val fingerprint = Fingerprint("Woooo", 100, 101L) var result: FingerprintAuthAttemptModel? = null - val job = launch { result = underTest.authenticate() } + val job = launch { result = authenticateInteractorImplUnderTest.authenticate() } val authCallback: ArgumentCaptor = argumentCaptor() @@ -284,7 +364,7 @@ class FingerprintManagerInteractorTest { fun testAuth_lockout() = testScope.runTest { var result: FingerprintAuthAttemptModel? = null - val job = launch { result = underTest.authenticate() } + val job = launch { result = authenticateInteractorImplUnderTest.authenticate() } val authCallback: ArgumentCaptor = argumentCaptor() @@ -314,7 +394,7 @@ class FingerprintManagerInteractorTest { val token = byteArrayOf(5, 3, 2) var result: FingerEnrollState? = null val job = launch { - underTest + enrollInteractorUnderTest .enroll(token, EnrollReason.FindSensor, FingerprintEnrollOptions.Builder().build()) .collect { result = it } } @@ -343,7 +423,7 @@ class FingerprintManagerInteractorTest { val token = byteArrayOf(5, 3, 2) var result: FingerEnrollState? = null val job = launch { - underTest + enrollInteractorUnderTest .enroll(token, EnrollReason.FindSensor, FingerprintEnrollOptions.Builder().build()) .collect { result = it } } @@ -372,7 +452,7 @@ class FingerprintManagerInteractorTest { val token = byteArrayOf(5, 3, 2) var result: FingerEnrollState? = null val job = launch { - underTest + enrollInteractorUnderTest .enroll(token, EnrollReason.FindSensor, FingerprintEnrollOptions.Builder().build()) .collect { result = it } } 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 9662c39d94a..04cece83cd0 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 @@ -99,9 +99,10 @@ class FingerprintEnrollFindSensorViewModelV2Test { backgroundViewModel.inForeground() enrollViewModel = FingerprintEnrollViewModel( - fakeFingerprintManagerInteractor, gatekeeperViewModel, navigationViewModel, + fakeFingerprintManagerInteractor, + fakeFingerprintManagerInteractor, ) accessibilityInteractor = object : AccessibilityInteractor { 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 index 46e883af33d..53f472691d6 100644 --- 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 @@ -49,8 +49,7 @@ class RFPSIconTouchViewModelTest { fun setup() { Dispatchers.setMain(backgroundDispatcher) testScope = TestScope(backgroundDispatcher) - rfpsIconTouchViewModel = - RFPSIconTouchViewModel() + rfpsIconTouchViewModel = RFPSIconTouchViewModel() } @After 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 index c475cc47faa..cf2deec5bac 100644 --- 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 @@ -88,9 +88,10 @@ class FingerprintEnrollEnrollingViewModelTest { backgroundViewModel.inForeground() val fingerprintEnrollViewModel = FingerprintEnrollViewModel( - fakeFingerprintManagerInteractor, gateKeeperViewModel, navigationViewModel, + fakeFingerprintManagerInteractor, + fakeFingerprintManagerInteractor, ) enrollEnrollingViewModel = FingerprintEnrollEnrollingViewModel(fingerprintEnrollViewModel, backgroundViewModel) 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 201fffa608a..88f76dd23f2 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 @@ -67,10 +67,11 @@ class FingerprintSettingsNavigationViewModelTest { underTest = FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory( defaultUserId, - fakeFingerprintManagerInteractor, backgroundDispatcher, null, null, + fakeFingerprintManagerInteractor, + fakeFingerprintManagerInteractor, ) .create(FingerprintSettingsNavigationViewModel::class.java) } @@ -272,10 +273,11 @@ class FingerprintSettingsNavigationViewModelTest { underTest = FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory( defaultUserId, - fakeFingerprintManagerInteractor, backgroundDispatcher, token, challenge, + fakeFingerprintManagerInteractor, + fakeFingerprintManagerInteractor, ) .create(FingerprintSettingsNavigationViewModel::class.java) @@ -299,10 +301,11 @@ class FingerprintSettingsNavigationViewModelTest { underTest = FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory( defaultUserId, - fakeFingerprintManagerInteractor, backgroundDispatcher, token, challenge, + fakeFingerprintManagerInteractor, + fakeFingerprintManagerInteractor, ) .create(FingerprintSettingsNavigationViewModel::class.java) @@ -331,10 +334,11 @@ class FingerprintSettingsNavigationViewModelTest { underTest = FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory( defaultUserId, - fakeFingerprintManagerInteractor, backgroundDispatcher, token, challenge, + fakeFingerprintManagerInteractor, + fakeFingerprintManagerInteractor, ) .create(FingerprintSettingsNavigationViewModel::class.java) 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 1618e16d904..79163d9a590 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 @@ -73,19 +73,25 @@ class FingerprintSettingsViewModelTest { navigationViewModel = FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory( defaultUserId, - fakeFingerprintManagerInteractor, backgroundDispatcher, null, null, + fakeFingerprintManagerInteractor, + fakeFingerprintManagerInteractor, ) .create(FingerprintSettingsNavigationViewModel::class.java) underTest = FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory( defaultUserId, - fakeFingerprintManagerInteractor, backgroundDispatcher, navigationViewModel, + fakeFingerprintManagerInteractor, + fakeFingerprintManagerInteractor, + fakeFingerprintManagerInteractor, + fakeFingerprintManagerInteractor, + fakeFingerprintManagerInteractor, + fakeFingerprintManagerInteractor, ) .create(FingerprintSettingsViewModel::class.java) } @@ -114,14 +120,7 @@ class FingerprintSettingsViewModelTest { fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf(FingerprintData("a", 1, 3L)) - underTest = - FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory( - defaultUserId, - fakeFingerprintManagerInteractor, - backgroundDispatcher, - navigationViewModel, - ) - .create(FingerprintSettingsViewModel::class.java) + recreateSettingsViewModel() var authAttempt: FingerprintAuthAttemptModel? = null val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } } @@ -156,14 +155,7 @@ class FingerprintSettingsViewModelTest { fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf(FingerprintData("a", 1, 3L)) - underTest = - FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory( - defaultUserId, - fakeFingerprintManagerInteractor, - backgroundDispatcher, - navigationViewModel, - ) - .create(FingerprintSettingsViewModel::class.java) + recreateSettingsViewModel() var authAttempt: FingerprintAuthAttemptModel? = null val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } } @@ -198,14 +190,7 @@ class FingerprintSettingsViewModelTest { val success = FingerprintAuthAttemptModel.Success(1) fakeFingerprintManagerInteractor.authenticateAttempt = success - underTest = - FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory( - defaultUserId, - fakeFingerprintManagerInteractor, - backgroundDispatcher, - navigationViewModel, - ) - .create(FingerprintSettingsViewModel::class.java) + recreateSettingsViewModel() var authAttempt: FingerprintAuthAttemptModel? = null @@ -225,14 +210,7 @@ class FingerprintSettingsViewModelTest { fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf(fingerprintToDelete) - underTest = - FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory( - defaultUserId, - fakeFingerprintManagerInteractor, - backgroundDispatcher, - navigationViewModel, - ) - .create(FingerprintSettingsViewModel::class.java) + recreateSettingsViewModel() var dialog: PreferenceViewModel? = null val dialogJob = launch { underTest.isShowingDialog.collect { dialog = it } } @@ -261,14 +239,7 @@ class FingerprintSettingsViewModelTest { fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf(fingerprintToRename) - underTest = - FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory( - defaultUserId, - fakeFingerprintManagerInteractor, - backgroundDispatcher, - navigationViewModel, - ) - .create(FingerprintSettingsViewModel::class.java) + recreateSettingsViewModel() var dialog: PreferenceViewModel? = null val dialogJob = launch { underTest.isShowingDialog.collect { dialog = it } } @@ -299,14 +270,7 @@ class FingerprintSettingsViewModelTest { fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf(fingerprintToDelete) - underTest = - FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory( - defaultUserId, - fakeFingerprintManagerInteractor, - backgroundDispatcher, - navigationViewModel, - ) - .create(FingerprintSettingsViewModel::class.java) + recreateSettingsViewModel() var dialog: PreferenceViewModel? = null val dialogJob = launch { underTest.isShowingDialog.collect { dialog = it } } @@ -390,6 +354,22 @@ class FingerprintSettingsViewModelTest { assertThat(authAttempt).isEqualTo(null) } + private fun recreateSettingsViewModel() { + underTest = + FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory( + defaultUserId, + backgroundDispatcher, + navigationViewModel, + fakeFingerprintManagerInteractor, + fakeFingerprintManagerInteractor, + fakeFingerprintManagerInteractor, + fakeFingerprintManagerInteractor, + fakeFingerprintManagerInteractor, + fakeFingerprintManagerInteractor, + ) + .create(FingerprintSettingsViewModel::class.java) + } + private fun setupAuth(): MutableList { fakeFingerprintManagerInteractor.sensorProp = FingerprintSensorPropertiesInternal( @@ -409,14 +389,7 @@ class FingerprintSettingsViewModelTest { val success = FingerprintAuthAttemptModel.Success(1) fakeFingerprintManagerInteractor.authenticateAttempt = success - underTest = - FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory( - defaultUserId, - fakeFingerprintManagerInteractor, - backgroundDispatcher, - navigationViewModel, - ) - .create(FingerprintSettingsViewModel::class.java) + recreateSettingsViewModel() return fingerprints } diff --git a/tests/unit/src/com/android/settings/wifi/repository/WifiHotspotRepositoryTest.java b/tests/unit/src/com/android/settings/wifi/repository/WifiHotspotRepositoryTest.java index 3571a36bde7..4765d180119 100644 --- a/tests/unit/src/com/android/settings/wifi/repository/WifiHotspotRepositoryTest.java +++ b/tests/unit/src/com/android/settings/wifi/repository/WifiHotspotRepositoryTest.java @@ -118,12 +118,12 @@ public class WifiHotspotRepositoryTest { mRepository.mSecurityType = mSecurityType; mRepository.mSpeedType = mSpeedType; mRepository.mIsDualBand = true; - mRepository.mBand5g.isUsableChannelsReady = true; - mRepository.mBand5g.isUsableChannelsUnsupported = false; - mRepository.mBand5g.hasUsableChannels = true; - mRepository.mBand6g.isUsableChannelsReady = true; - mRepository.mBand6g.isUsableChannelsUnsupported = false; - mRepository.mBand6g.hasUsableChannels = true; + mRepository.mBand5g.isChannelsReady = true; + mRepository.mBand5g.isChannelsUnsupported = false; + mRepository.mBand5g.hasChannels = true; + mRepository.mBand6g.isChannelsReady = true; + mRepository.mBand6g.isChannelsUnsupported = false; + mRepository.mBand6g.hasChannels = true; } @Test @@ -382,7 +382,7 @@ public class WifiHotspotRepositoryTest { @Test public void updateSpeedType_singleBand5gPreferredBut5gUnavailable_get2gSpeedType() { mRepository.mIsDualBand = false; - mRepository.mBand5g.hasUsableChannels = false; + mRepository.mBand5g.hasChannels = false; SoftApConfiguration config = new SoftApConfiguration.Builder() .setBand(WIFI_5GHZ_BAND_PREFERRED).build(); when(mWifiManager.getSoftApConfiguration()).thenReturn(config); @@ -407,7 +407,7 @@ public class WifiHotspotRepositoryTest { @Test public void updateSpeedType_singleBand6gPreferredBut6gUnavailable_get5gSpeedType() { mRepository.mIsDualBand = false; - mRepository.mBand6g.hasUsableChannels = false; + mRepository.mBand6g.hasChannels = false; SoftApConfiguration config = new SoftApConfiguration.Builder() .setBand(WIFI_6GHZ_BAND_PREFERRED).build(); when(mWifiManager.getSoftApConfiguration()).thenReturn(config); @@ -420,8 +420,8 @@ public class WifiHotspotRepositoryTest { @Test public void updateSpeedType_singleBand6gPreferredBut5gAnd6gUnavailable_get2gSpeedType() { mRepository.mIsDualBand = false; - mRepository.mBand5g.hasUsableChannels = false; - mRepository.mBand6g.hasUsableChannels = false; + mRepository.mBand5g.hasChannels = false; + mRepository.mBand6g.hasChannels = false; SoftApConfiguration config = new SoftApConfiguration.Builder() .setBand(WIFI_6GHZ_BAND_PREFERRED).build(); when(mWifiManager.getSoftApConfiguration()).thenReturn(config); @@ -446,7 +446,7 @@ public class WifiHotspotRepositoryTest { @Test public void updateSpeedType_dualBand2gAnd5gBut5gUnavailable_get2gSpeedType() { mRepository.mIsDualBand = true; - mRepository.mBand5g.hasUsableChannels = false; + mRepository.mBand5g.hasChannels = false; SoftApConfiguration config = new SoftApConfiguration.Builder() .setBand(WIFI_5GHZ_BAND_PREFERRED).build(); when(mWifiManager.getSoftApConfiguration()).thenReturn(config); @@ -562,19 +562,19 @@ public class WifiHotspotRepositoryTest { } @Test - public void is5gAvailable_hasUsableChannels_returnTrue() { + public void is5gAvailable_hasChannels_returnTrue() { mRepository.mIs5gBandSupported = true; // Reset m5gBand to trigger an update - mRepository.mBand5g.isUsableChannelsReady = false; + mRepository.mBand5g.isChannelsReady = false; assertThat(mRepository.is5gAvailable()).isTrue(); } @Test - public void is5gAvailable_noUsableChannels_returnFalse() { + public void is5gAvailable_noChannels_returnFalse() { mRepository.mIs5gBandSupported = true; // Reset m5gBand to trigger an update - mRepository.mBand5g.isUsableChannelsReady = false; + mRepository.mBand5g.isChannelsReady = false; when(mWifiManager.getAllowedChannels(WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS, OP_MODE_SAP)) .thenReturn(null); @@ -585,7 +585,7 @@ public class WifiHotspotRepositoryTest { @UiThreadTest public void get5gAvailable_shouldNotReturnNull() { // Reset m5gBand to trigger an update - mRepository.mBand5g.isUsableChannelsReady = false; + mRepository.mBand5g.isChannelsReady = false; assertThat(mRepository.get5gAvailable()).isNotNull(); } @@ -606,19 +606,19 @@ public class WifiHotspotRepositoryTest { } @Test - public void is6gAvailable_hasUsableChannels_returnTrue() { + public void is6gAvailable_hasChannels_returnTrue() { mRepository.mIs6gBandSupported = true; // Reset m6gBand to trigger an update - mRepository.mBand6g.isUsableChannelsReady = false; + mRepository.mBand6g.isChannelsReady = false; assertThat(mRepository.is6gAvailable()).isTrue(); } @Test - public void is6gAvailable_noUsableChannels_returnFalse() { + public void is6gAvailable_noChannels_returnFalse() { mRepository.mIs6gBandSupported = true; // Reset m6gBand to trigger an update - mRepository.mBand6g.isUsableChannelsReady = false; + mRepository.mBand6g.isChannelsReady = false; when(mWifiManager.getAllowedChannels(WifiScanner.WIFI_BAND_6_GHZ, OP_MODE_SAP)) .thenReturn(null); @@ -658,33 +658,33 @@ public class WifiHotspotRepositoryTest { } @Test - public void isChannelAvailable_throwIllegalArgumentException_hasUsableChannelsFalse() { + public void isChannelAvailable_throwIllegalArgumentException_hasChannelsFalse() { doThrow(IllegalArgumentException.class).when(mWifiManager) .getAllowedChannels(WifiScanner.WIFI_BAND_6_GHZ, OP_MODE_SAP); mRepository.isChannelAvailable(mRepository.mBand6g); - assertThat(mRepository.mBand6g.hasUsableChannels).isFalse(); - assertThat(mRepository.mBand6g.isUsableChannelsUnsupported).isTrue(); + assertThat(mRepository.mBand6g.hasChannels).isFalse(); + assertThat(mRepository.mBand6g.isChannelsUnsupported).isTrue(); } @Test - public void isChannelAvailable_throwUnsupportedOperationException_hasUsableChannelsFalse() { + public void isChannelAvailable_throwUnsupportedOperationException_hasChannelsFalse() { doThrow(UnsupportedOperationException.class).when(mWifiManager) .getAllowedChannels(WifiScanner.WIFI_BAND_6_GHZ, OP_MODE_SAP); mRepository.isChannelAvailable(mRepository.mBand6g); - assertThat(mRepository.mBand6g.hasUsableChannels).isFalse(); - assertThat(mRepository.mBand6g.isUsableChannelsUnsupported).isTrue(); + assertThat(mRepository.mBand6g.hasChannels).isFalse(); + assertThat(mRepository.mBand6g.isChannelsUnsupported).isTrue(); } @Test - public void isChannelAvailable_noExceptionAndHasUsableChannels_hasUsableChannelsTrue() { + public void isChannelAvailable_noExceptionAndHasChannels_hasChannelsTrue() { mRepository.isChannelAvailable(mRepository.mBand6g); - assertThat(mRepository.mBand6g.hasUsableChannels).isTrue(); - assertThat(mRepository.mBand6g.isUsableChannelsUnsupported).isFalse(); + assertThat(mRepository.mBand6g.hasChannels).isTrue(); + assertThat(mRepository.mBand6g.isChannelsUnsupported).isFalse(); } @Test @@ -744,9 +744,9 @@ public class WifiHotspotRepositoryTest { } @Test - public void updateCapabilityChanged_band5gUsableChannelsUnsupported_update5gAvailable() { + public void updateCapabilityChanged_band5gChannelsUnsupported_update5gAvailable() { mRepository = spy(new WifiHotspotRepository(mContext, mWifiManager, mTetheringManager)); - mRepository.mBand5g.isUsableChannelsUnsupported = true; + mRepository.mBand5g.isChannelsUnsupported = true; mRepository.updateCapabilityChanged(); @@ -755,9 +755,9 @@ public class WifiHotspotRepositoryTest { } @Test - public void updateCapabilityChanged_band6gUsableChannelsUnsupported_update5gAvailable() { + public void updateCapabilityChanged_band6gChannelsUnsupported_update5gAvailable() { mRepository = spy(new WifiHotspotRepository(mContext, mWifiManager, mTetheringManager)); - mRepository.mBand6g.isUsableChannelsUnsupported = true; + mRepository.mBand6g.isChannelsUnsupported = true; mRepository.updateCapabilityChanged(); @@ -766,18 +766,18 @@ public class WifiHotspotRepositoryTest { } @Test - public void isAvailable_isUsableChannelsUnsupportedFalse_returnHasUsableChannels() { - mRepository.mBand6g.isUsableChannelsUnsupported = false; - mRepository.mBand6g.hasUsableChannels = false; + public void isAvailable_isChannelsUnsupportedFalse_returnHasChannels() { + mRepository.mBand6g.isChannelsUnsupported = false; + mRepository.mBand6g.hasChannels = false; mRepository.mBand6g.hasCapability = true; assertThat(mRepository.mBand6g.isAvailable()).isFalse(); } @Test - public void isAvailable_isUsableChannelsUnsupportedTrue_returnHasCapability() { - mRepository.mBand6g.isUsableChannelsUnsupported = true; - mRepository.mBand6g.hasUsableChannels = false; + public void isAvailable_isChannelsUnsupportedTrue_returnHasCapability() { + mRepository.mBand6g.isChannelsUnsupported = true; + mRepository.mBand6g.hasChannels = false; mRepository.mBand6g.hasCapability = true; assertThat(mRepository.mBand6g.isAvailable()).isTrue();