From 7443e2dfe84e01ce2491041b5d75153dc8b4eeb3 Mon Sep 17 00:00:00 2001 From: Alice Kuo Date: Sat, 11 May 2024 03:12:57 +0800 Subject: [PATCH 01/17] Avoid update the le audio mode as bluetooth turn off 1. Fix the wrong "disable" setting as bluetooth state is off 2. disable the switcher as bluetooth state is disabled Bug: 336448875 Test: atest Change-Id: I34d9f6aef11712cad16ff105c5859a694f1fe9c9 --- .../BluetoothLeAudioModePreferenceController.java | 13 +++++++++---- ...luetoothLeAudioModePreferenceControllerTest.java | 12 ++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/development/BluetoothLeAudioModePreferenceController.java b/src/com/android/settings/development/BluetoothLeAudioModePreferenceController.java index 06cfe65043e..739258d3fda 100644 --- a/src/com/android/settings/development/BluetoothLeAudioModePreferenceController.java +++ b/src/com/android/settings/development/BluetoothLeAudioModePreferenceController.java @@ -96,17 +96,17 @@ public class BluetoothLeAudioModePreferenceController return; } + String currentValue; if (mBluetoothAdapter.isLeAudioBroadcastSourceSupported() == BluetoothStatusCodes.FEATURE_SUPPORTED) { - SystemProperties.set(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, "broadcast"); + currentValue = "broadcast"; } else if (mBluetoothAdapter.isLeAudioSupported() == BluetoothStatusCodes.FEATURE_SUPPORTED) { - SystemProperties.set(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, "unicast"); + currentValue = "unicast"; } else { - SystemProperties.set(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, "disabled"); + currentValue = "disabled"; } - final String currentValue = SystemProperties.get(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY); int index = 0; for (int i = 0; i < mListValues.length; i++) { if (TextUtils.equals(currentValue, mListValues[i])) { @@ -118,6 +118,11 @@ public class BluetoothLeAudioModePreferenceController final ListPreference listPreference = (ListPreference) preference; listPreference.setValue(mListValues[index]); listPreference.setSummary(mListSummaries[index]); + + if (!mBluetoothAdapter.isEnabled()) { + listPreference.setEnabled(false); + return; + } } /** diff --git a/tests/robotests/src/com/android/settings/development/BluetoothLeAudioModePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/BluetoothLeAudioModePreferenceControllerTest.java index f35fb17f8ca..965b34e7370 100644 --- a/tests/robotests/src/com/android/settings/development/BluetoothLeAudioModePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/BluetoothLeAudioModePreferenceControllerTest.java @@ -105,4 +105,16 @@ public class BluetoothLeAudioModePreferenceControllerTest { assertThat(SystemProperties.get(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, mListValues[0]) .equals(mController.mNewMode)).isFalse(); } + + @Test + public void onBluetoothTurnOff_shouldNotChangeLeAudioMode() { + SystemProperties.set(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, mListValues[1]); + when(mBluetoothAdapter.isEnabled()) + .thenReturn(false); + + mController.updateState(mPreference); + final String mode = SystemProperties + .get(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, mListValues[0]); + assertThat(mode.equals(mListValues[1])).isTrue(); + } } From 0336781be068e5eed309702ccf7be47362f44d3b Mon Sep 17 00:00:00 2001 From: Joshua McCloskey Date: Mon, 18 Mar 2024 23:23:43 +0000 Subject: [PATCH 02/17] UDFPS Enrollment Refactor (4/N) Accessibility + text/dpi change + rotation should be properly handled. Debug repos were added to make UI developemnt for UDFPS much easier(not requiring calls to fingerprint manager). Change-Id: I89900cea0d9e953124781cdf308fb38858de5d16 --- AndroidManifest.xml | 1 + .../fingerprint_v2_udfps_enroll_enrolling.xml | 100 +++++ .../fingerprint_v2_udfps_enroll_enrolling.xml | 2 +- .../data/repository/DebuggingRepository.kt | 46 +++ .../SimulatedTouchEventsRepository.kt | 31 ++ .../repository/UdfpsEnrollDebugRepository.kt | 127 +++++++ .../domain/interactor/DebuggingInteractor.kt | 41 ++ .../interactor/DisplayDensityInteractor.kt | 76 ++++ .../interactor/EnrollStageInteractor.kt | 43 +++ .../interactor/FingerprintEnrollInteractor.kt | 134 +++++++ .../FingerprintManagerInteractorImpl.kt | 114 +----- .../interactor/OrientationInteractor.kt | 39 +- .../domain/interactor/VibrationInteractor.kt | 75 ++++ .../FingerprintManagerInteractor.kt | 3 +- .../lib/model/FingerEnrollState.kt | 12 + .../viewmodel => lib/model}/StageViewModel.kt | 2 +- .../FingerprintEnrollmentV2Activity.kt | 101 ++++- .../education/RfpsEnrollFindSensorFragment.kt | 132 +++++++ .../education/SfpsEnrollFindSensorFragment.kt | 162 ++++++++ .../UdfpsEnrollFindSensorFragment.kt} | 105 ++---- .../common/util/FingerprintOptionUtil.kt | 31 ++ .../widget/FingerprintErrorDialog.kt | 7 +- .../rfps/ui/fragment/RFPSEnrollFragment.kt | 2 +- .../rfps/ui/viewmodel/RFPSViewModel.kt | 6 +- .../udfps/ui/fragment/UdfpsEnrollFragment.kt | 76 +++- .../udfps/ui/viewmodel/DescriptionText.kt | 2 + .../ui/viewmodel/EducationAnimationModel.kt | 2 + .../udfps/ui/viewmodel/HeaderText.kt | 2 + .../udfps/ui/viewmodel/UdfpsEnrollEvent.kt | 41 -- .../udfps/ui/viewmodel/UdfpsViewModel.kt | 353 ++++++++++++------ .../udfps/ui/widget/UdfpsEnrollHelperV2.kt | 5 +- .../udfps/ui/widget/UdfpsEnrollIconV2.kt | 77 ++-- .../UdfpsEnrollProgressBarDrawableV2.kt | 233 +++++++----- .../udfps/ui/widget/UdfpsEnrollViewV2.kt | 162 ++++++-- .../FingerprintEnrollEnrollingViewModel.kt | 4 +- .../FingerprintEnrollFindSensorViewModel.kt | 6 +- .../fragment/FingerprintSettingsV2Fragment.kt | 15 +- .../biometrics/fingerprint/Injector.kt | 1 + ... => RfpsEnrollFindSensorScreenshotTest.kt} | 11 +- .../FingerprintManagerInteractorTest.kt | 15 +- ...gerprintEnrollFindSensorViewModelV2Test.kt | 3 +- 41 files changed, 1816 insertions(+), 584 deletions(-) create mode 100644 res/layout-land/fingerprint_v2_udfps_enroll_enrolling.xml create mode 100644 src/com/android/settings/biometrics/fingerprint2/data/repository/DebuggingRepository.kt create mode 100644 src/com/android/settings/biometrics/fingerprint2/data/repository/SimulatedTouchEventsRepository.kt create mode 100644 src/com/android/settings/biometrics/fingerprint2/data/repository/UdfpsEnrollDebugRepository.kt create mode 100644 src/com/android/settings/biometrics/fingerprint2/domain/interactor/DebuggingInteractor.kt create mode 100644 src/com/android/settings/biometrics/fingerprint2/domain/interactor/DisplayDensityInteractor.kt create mode 100644 src/com/android/settings/biometrics/fingerprint2/domain/interactor/EnrollStageInteractor.kt create mode 100644 src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintEnrollInteractor.kt create mode 100644 src/com/android/settings/biometrics/fingerprint2/domain/interactor/VibrationInteractor.kt rename src/com/android/settings/biometrics/fingerprint2/{ui/enrollment/modules/enrolling/udfps/ui/viewmodel => lib/model}/StageViewModel.kt (94%) create mode 100644 src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/RfpsEnrollFindSensorFragment.kt create mode 100644 src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/SfpsEnrollFindSensorFragment.kt rename src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/{FingerprintEnrollFindSensorV2Fragment.kt => education/UdfpsEnrollFindSensorFragment.kt} (66%) create mode 100644 src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/common/util/FingerprintOptionUtil.kt rename src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/{rfps/ui => common}/widget/FingerprintErrorDialog.kt (96%) delete mode 100644 src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsEnrollEvent.kt rename tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/fragment/{FingerprintEnrollFindSensorScreenshotTest.kt => RfpsEnrollFindSensorScreenshotTest.kt} (73%) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f629a365099..a438fdd1f76 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2788,6 +2788,7 @@ diff --git a/res/layout-land/fingerprint_v2_udfps_enroll_enrolling.xml b/res/layout-land/fingerprint_v2_udfps_enroll_enrolling.xml new file mode 100644 index 00000000000..86768d6b43f --- /dev/null +++ b/res/layout-land/fingerprint_v2_udfps_enroll_enrolling.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/fingerprint_v2_udfps_enroll_enrolling.xml b/res/layout/fingerprint_v2_udfps_enroll_enrolling.xml index ddd2c30d807..ab8fb2c3ae5 100644 --- a/res/layout/fingerprint_v2_udfps_enroll_enrolling.xml +++ b/res/layout/fingerprint_v2_udfps_enroll_enrolling.xml @@ -18,7 +18,7 @@ +} diff --git a/src/com/android/settings/biometrics/fingerprint2/data/repository/UdfpsEnrollDebugRepository.kt b/src/com/android/settings/biometrics/fingerprint2/data/repository/UdfpsEnrollDebugRepository.kt new file mode 100644 index 00000000000..9b74813e33f --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/data/repository/UdfpsEnrollDebugRepository.kt @@ -0,0 +1,127 @@ +/* + * 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.graphics.Point +import android.graphics.Rect +import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractor +import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason +import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState +import com.android.systemui.biometrics.shared.model.FingerprintSensor +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.SensorStrength +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf + +/** + * This class is used to simulate enroll data. This has two major use cases. 1). Ease of Development + * 2). Bug Fixes + */ +class UdfpsEnrollDebugRepositoryImpl : + FingerprintEnrollInteractor, FingerprintSensorRepository, SimulatedTouchEventsRepository { + + override suspend fun enroll(hardwareAuthToken: ByteArray?, enrollReason: EnrollReason) = flow { + emit(FingerEnrollState.OverlayShown) + delay(200) + emit(FingerEnrollState.EnrollHelp(helpMsgId, "Hello world")) + delay(200) + emit(FingerEnrollState.EnrollProgress(15, 16)) + delay(300) + emit(FingerEnrollState.EnrollHelp(helpMsgId, "Hello world")) + delay(1000) + emit(FingerEnrollState.EnrollProgress(14, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(13, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(12, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(11, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(10, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(9, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(8, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(7, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(6, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(5, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(4, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(3, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(2, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(1, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(0, 16)) + } + + /** Provides touch events to the UdfpsEnrollFragment */ + override val touchExplorationDebug: Flow = flow { + delay(2000) + emit(pointToLeftOfSensor(sensorRect)) + delay(2000) + emit(pointBelowSensor(sensorRect)) + delay(2000) + emit(pointToRightOfSensor(sensorRect)) + delay(2000) + emit(pointAboveSensor(sensorRect)) + } + + override val fingerprintSensor: Flow = flowOf(sensorProps) + + private fun pointToLeftOfSensor(sensorLocation: Rect) = + Point(sensorLocation.right + 5, sensorLocation.centerY()) + + private fun pointToRightOfSensor(sensorLocation: Rect) = + Point(sensorLocation.left - 5, sensorLocation.centerY()) + + private fun pointBelowSensor(sensorLocation: Rect) = + Point(sensorLocation.centerX(), sensorLocation.bottom + 5) + + private fun pointAboveSensor(sensorLocation: Rect) = + Point(sensorLocation.centerX(), sensorLocation.top - 5) + + companion object { + + private val helpMsgId: Int = 1 + private val sensorLocationInternal = Pair(540, 1713) + private val sensorRadius = 100 + private val sensorRect = + Rect( + this.sensorLocationInternal.first - sensorRadius, + this.sensorLocationInternal.second - sensorRadius, + this.sensorLocationInternal.first + sensorRadius, + this.sensorLocationInternal.second + sensorRadius, + ) + val sensorProps = + FingerprintSensor( + 1, + SensorStrength.STRONG, + 5, + FingerprintSensorType.UDFPS_OPTICAL, + sensorRect, + sensorRadius, + ) + } +} diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/DebuggingInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/DebuggingInteractor.kt new file mode 100644 index 00000000000..3edca96b4a2 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/DebuggingInteractor.kt @@ -0,0 +1,41 @@ +/* + * 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.DebuggingRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow + +/** Interactor indicating if certain debug flows are enabled. */ +interface DebuggingInteractor { + /** This indicates that certain debug flows are enabled. */ + val debuggingEnabled: Flow + /** This indicates if udfps should instead use debug repos to supply data to its various views. */ + val udfpsEnrollmentDebuggingEnabled: Flow +} + +/** + * This interactor essentially forwards the [DebuggingRepository] + */ +class DebuggingInteractorImpl(val debuggingRepository: DebuggingRepository) : DebuggingInteractor { + override val debuggingEnabled: Flow = flow { + emit(debuggingRepository.isDebuggingEnabled()) + } + override val udfpsEnrollmentDebuggingEnabled: Flow = flow { + emit(debuggingRepository.isUdfpsEnrollmentDebuggingEnabled()) + } +} diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/DisplayDensityInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/DisplayDensityInteractor.kt new file mode 100644 index 00000000000..67c00017136 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/DisplayDensityInteractor.kt @@ -0,0 +1,76 @@ +/* + * 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 kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.update + +/** + * This class is responsible for handling updates to fontScale and displayDensity and forwarding + * these events to classes that need them + */ +interface DisplayDensityInteractor { + /** Indicates the display density has been updated. */ + fun updateDisplayDensity(density: Int) + + /** Indicates the font scale has been updates. */ + fun updateFontScale(fontScale: Float) + + /** A flow that propagates fontscale. */ + val fontScale: Flow + + /** A flow that propagates displayDensity. */ + val displayDensity: Flow + + /** A flow that propagates the default display density. */ + val defaultDisplayDensity: Flow +} + +/** + * Implementation of the [DisplayDensityInteractor]. This interactor is used to forward activity + * information to the rest of the application. + */ +class DisplayDensityInteractorImpl( + currentFontScale: Float, + currentDisplayDensity: Int, + defaultDisplayDensity: Int, + scope: CoroutineScope, +) : DisplayDensityInteractor { + override fun updateDisplayDensity(density: Int) { + _displayDensity.update { density } + } + + override fun updateFontScale(fontScale: Float) { + _fontScale.update { fontScale } + } + + private val _fontScale = MutableStateFlow(currentFontScale) + private val _displayDensity = MutableStateFlow(currentDisplayDensity) + + override val fontScale: Flow = _fontScale.asStateFlow() + + override val displayDensity: Flow = _displayDensity.asStateFlow() + + override val defaultDisplayDensity: Flow = + flowOf(defaultDisplayDensity).shareIn(scope, SharingStarted.Eagerly, 1) +} diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/EnrollStageInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/EnrollStageInteractor.kt new file mode 100644 index 00000000000..2d4cb409868 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/EnrollStageInteractor.kt @@ -0,0 +1,43 @@ +/* + * 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.lib.model.StageViewModel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +typealias EnrollStageThresholds = Map + +/** Interactor that provides enroll stages for enrollment. */ +interface EnrollStageInteractor { + + /** Provides enroll stages for enrollment. */ + val enrollStageThresholds: Flow +} + +class EnrollStageInteractorImpl() : EnrollStageInteractor { + override val enrollStageThresholds: Flow = + flowOf( + mapOf( + 0.0f to StageViewModel.Center, + 0.25f to StageViewModel.Guided, + 0.5f to StageViewModel.Fingertip, + 0.75f to StageViewModel.LeftEdge, + 0.875f to StageViewModel.RightEdge, + ) + ) +} diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintEnrollInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintEnrollInteractor.kt new file mode 100644 index 00000000000..e4180d337b1 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintEnrollInteractor.kt @@ -0,0 +1,134 @@ +/* + * 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.Context +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.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 + +/** This repository is responsible for collecting all state related to the enroll API. */ +interface FingerprintEnrollInteractor { + + /** + * By calling this function, [fingerEnrollState] will begin to be populated with data on success. + */ + suspend fun enroll( + hardwareAuthToken: ByteArray?, + enrollReason: EnrollReason, + ): Flow +} + +class FingerprintEnrollInteractorImpl( + private val applicationContext: Context, + private val fingerprintEnrollOptions: FingerprintEnrollOptions, + private val fingerprintManager: FingerprintManager, + private val fingerprintFlow: FingerprintFlow, +) : FingerprintEnrollInteractor { + private val enrollRequestOutstanding = MutableStateFlow(false) + + override suspend fun enroll( + hardwareAuthToken: ByteArray?, + enrollReason: EnrollReason, + ): 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 } + } + } + + val cancellationSignal = CancellationSignal() + + fingerprintManager.enroll( + hardwareAuthToken, + cancellationSignal, + applicationContext.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/FingerprintManagerInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt index 652bc0c312f..ca3665ce8ca 100644 --- a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt @@ -18,43 +18,25 @@ package com.android.settings.biometrics.fingerprint2.domain.interactor import android.content.Context import android.content.Intent -import android.hardware.biometrics.BiometricConstants; -import android.hardware.biometrics.BiometricFingerprintConstants -import android.hardware.biometrics.SensorLocationInternal -import android.hardware.fingerprint.FingerprintEnrollOptions; import android.hardware.fingerprint.FingerprintManager import android.hardware.fingerprint.FingerprintManager.GenerateChallengeCallback import android.hardware.fingerprint.FingerprintManager.RemovalCallback -import android.hardware.fingerprint.FingerprintSensorPropertiesInternal import android.os.CancellationSignal import android.util.Log import com.android.settings.biometrics.GatekeeperPasswordProvider -import com.android.settings.biometrics.BiometricUtils -import com.android.settings.biometrics.fingerprint2.conversion.Util.toEnrollError -import com.android.settings.biometrics.fingerprint2.conversion.Util.toOriginalReason 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.biometrics.fingerprint2.lib.model.FingerprintFlow -import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard import com.android.settings.password.ChooseLockSettingsHelper -import com.android.systemui.biometrics.shared.model.toFingerprintSensor -import com.google.android.setupcompat.util.WizardManagerHelper import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.channels.onFailure -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.update import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext @@ -66,9 +48,7 @@ class FingerprintManagerInteractorImpl( private val fingerprintManager: FingerprintManager, fingerprintSensorRepository: FingerprintSensorRepository, private val gatekeeperPasswordProvider: GatekeeperPasswordProvider, - private val pressToAuthInteractor: PressToAuthInteractor, - private val fingerprintFlow: FingerprintFlow, - private val intent: Intent, + private val fingerprintEnrollStateRepository: FingerprintEnrollInteractor, ) : FingerprintManagerInteractor { private val maxFingerprints = @@ -77,7 +57,6 @@ class FingerprintManagerInteractorImpl( ) private val applicationContext = applicationContext.applicationContext - private val enrollRequestOutstanding = MutableStateFlow(false) override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair = suspendCoroutine { @@ -113,85 +92,8 @@ class FingerprintManagerInteractorImpl( override val maxEnrollableFingerprints = flow { emit(maxFingerprints) } - override suspend fun enroll( - hardwareAuthToken: ByteArray?, - enrollReason: EnrollReason, - ): 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 } - } - } - - val cancellationSignal = CancellationSignal() - - if (intent.getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1) === -1) { - val isSuw: Boolean = WizardManagerHelper.isAnySetupWizard(intent) - intent.putExtra(BiometricUtils.EXTRA_ENROLL_REASON, - if (isSuw) FingerprintEnrollOptions.ENROLL_REASON_SUW else - FingerprintEnrollOptions.ENROLL_REASON_SETTINGS) - } - - fingerprintManager.enroll( - hardwareAuthToken, - cancellationSignal, - applicationContext.userId, - enrollmentCallback, - enrollReason.toOriginalReason(), - toFingerprintEnrollOptions(intent) - ) - 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() - } - } - } + override suspend fun enroll(hardwareAuthToken: ByteArray?, enrollReason: EnrollReason): Flow = + fingerprintEnrollStateRepository.enroll(hardwareAuthToken, enrollReason) override suspend fun removeFingerprint(fp: FingerprintData): Boolean = suspendCoroutine { val callback = @@ -263,14 +165,4 @@ class FingerprintManagerInteractorImpl( ) } - private fun toFingerprintEnrollOptions(intent: Intent): FingerprintEnrollOptions { - val reason: Int = - intent.getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1) - val builder: FingerprintEnrollOptions.Builder = FingerprintEnrollOptions.Builder() - builder.setEnrollReason(FingerprintEnrollOptions.ENROLL_REASON_UNKNOWN) - if (reason != -1) { - builder.setEnrollReason(reason) - } - return builder.build() - } } diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/OrientationInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/OrientationInteractor.kt index 968203fff80..5d1d8c86e37 100644 --- a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/OrientationInteractor.kt +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/OrientationInteractor.kt @@ -17,6 +17,7 @@ package com.android.settings.biometrics.fingerprint2.domain.interactor import android.content.Context +import android.util.Log import android.view.OrientationEventListener import com.android.internal.R import kotlinx.coroutines.CoroutineScope @@ -24,16 +25,23 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.transform -/** - * Interactor which provides information about orientation - */ +/** Interactor which provides information about orientation */ interface OrientationInteractor { /** A flow that contains the information about the orientation changing */ val orientation: Flow - /** A flow that contains the rotation info */ + /** + * A flow that contains the rotation info + */ val rotation: Flow + /** + * A flow that contains the rotation info matched against the def [config_reverseDefaultRotation] + */ + val rotationFromDefault: Flow /** * A Helper function that computes rotation if device is in * [R.bool.config_reverseDefaultConfigRotation] @@ -53,24 +61,11 @@ class OrientationInteractorImpl(private val context: Context, activityScope: Cor } orientationEventListener.enable() awaitClose { orientationEventListener.disable() } - } + }.shareIn(activityScope, SharingStarted.Eagerly, replay = 1) - override val rotation: Flow = - callbackFlow { - val orientationEventListener = - object : OrientationEventListener(context) { - override fun onOrientationChanged(orientation: Int) { - trySend(getRotationFromDefault(context.display!!.rotation)) - } - } - orientationEventListener.enable() - awaitClose { orientationEventListener.disable() } - } - .stateIn( - activityScope, // This is tied to the activity scope - SharingStarted.WhileSubscribed(), // When no longer subscribed, we removeTheListener - context.display!!.rotation, - ) + override val rotation: Flow = orientation.transform { emit(context.display!!.rotation) } + + override val rotationFromDefault: Flow = rotation.map { getRotationFromDefault(it) } override fun getRotationFromDefault(rotation: Int): Int { val isReverseDefaultRotation = @@ -81,4 +76,4 @@ class OrientationInteractorImpl(private val context: Context, activityScope: Cor rotation } } -} \ No newline at end of file +} diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/VibrationInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/VibrationInteractor.kt new file mode 100644 index 00000000000..0f107cc6e36 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/VibrationInteractor.kt @@ -0,0 +1,75 @@ +/* + * 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.Context +import android.os.Process +import android.os.VibrationAttributes +import android.os.VibrationEffect +import android.os.Vibrator + +/** Indicates the possible vibration effects for fingerprint enrollment */ +sealed class FingerprintVibrationEffects { + /** A vibration indicating an error */ + data object UdfpsError : FingerprintVibrationEffects() + + /** + * A vibration indicating success, this usually occurs when progress on the UDFPS enrollment has + * been made + */ + data object UdfpsSuccess : FingerprintVibrationEffects() + + /** This vibration typically occurs when a help message is shown during UDFPS enrollment */ + data object UdfpsHelp : FingerprintVibrationEffects() +} +/** Interface for sending haptic feedback */ +interface VibrationInteractor { + /** This will send a haptic vibration */ + fun vibrate(effect: FingerprintVibrationEffects, caller: String) +} + +/** Implementation of the VibrationInteractor interface */ +class VibrationInteractorImpl(val vibrator: Vibrator, val applicationContext: Context) : + VibrationInteractor { + override fun vibrate(effect: FingerprintVibrationEffects, caller: String) { + val callerString = "$caller::$effect" + val res = + when (effect) { + FingerprintVibrationEffects.UdfpsHelp, + FingerprintVibrationEffects.UdfpsError -> + Pair(VIBRATE_EFFECT_ERROR, FINGERPRINT_ENROLLING_SONIFICATION_ATTRIBUTES) + FingerprintVibrationEffects.UdfpsSuccess -> + Pair(VIBRATE_EFFECT_SUCCESS, HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES) + } + vibrator.vibrate( + Process.myUid(), + applicationContext.opPackageName, + res.first, + callerString, + res.second, + ) + } + + companion object { + private val VIBRATE_EFFECT_ERROR = VibrationEffect.createWaveform(longArrayOf(0, 5, 55, 60), -1) + private val FINGERPRINT_ENROLLING_SONIFICATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ACCESSIBILITY) + private val HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK) + private val VIBRATE_EFFECT_SUCCESS = VibrationEffect.get(VibrationEffect.EFFECT_CLICK) + } +} 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 index 105929dd33b..d2f9f0ae014 100644 --- a/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/FingerprintManagerInteractor.kt +++ b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/FingerprintManagerInteractor.kt @@ -57,8 +57,7 @@ interface FingerprintManagerInteractor { /** * Runs [FingerprintManager.enroll] with the [hardwareAuthToken] and [EnrollReason] for this - * enrollment. Returning the [FingerEnrollState] that represents this fingerprint enrollment - * state. + * enrollment. If successful data in the [fingerprintEnrollState] should be populated. */ suspend fun enroll( hardwareAuthToken: ByteArray?, diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/model/FingerEnrollState.kt b/src/com/android/settings/biometrics/fingerprint2/lib/model/FingerEnrollState.kt index 683397fce02..24a9a86f339 100644 --- a/src/com/android/settings/biometrics/fingerprint2/lib/model/FingerEnrollState.kt +++ b/src/com/android/settings/biometrics/fingerprint2/lib/model/FingerEnrollState.kt @@ -42,4 +42,16 @@ sealed class FingerEnrollState { val shouldRetryEnrollment: Boolean, val isCancelled: Boolean, ) : FingerEnrollState() + + /** Indicates an acquired event has occurred */ + data class Acquired(val acquiredGood: Boolean) : FingerEnrollState() + + /** Indicates a pointer down event has occurred */ + data object PointerDown : FingerEnrollState() + + /** Indicates a pointer up event has occurred */ + data object PointerUp : FingerEnrollState() + + /** Indicates the overlay has shown */ + data object OverlayShown : FingerEnrollState() } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/StageViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/lib/model/StageViewModel.kt similarity index 94% rename from src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/StageViewModel.kt rename to src/com/android/settings/biometrics/fingerprint2/lib/model/StageViewModel.kt index 75eaec786b4..81bba157291 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/StageViewModel.kt +++ b/src/com/android/settings/biometrics/fingerprint2/lib/model/StageViewModel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel +package com.android.settings.biometrics.fingerprint2.lib.model /** * A view model that describes the various stages of UDFPS Enrollment. This stages typically update 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 70d58eab776..c8e9ca38d85 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 @@ -19,8 +19,10 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.activity import android.app.Activity import android.content.Intent import android.content.res.Configuration +import android.hardware.fingerprint.FingerprintEnrollOptions import android.hardware.fingerprint.FingerprintManager import android.os.Bundle +import android.os.Vibrator import android.util.Log import android.view.accessibility.AccessibilityManager import androidx.activity.result.contract.ActivityResultContracts @@ -35,21 +37,35 @@ import com.android.settings.Utils.SETTINGS_PACKAGE_NAME import com.android.settings.biometrics.BiometricEnrollBase import com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED +import com.android.settings.biometrics.BiometricUtils import com.android.settings.biometrics.GatekeeperPasswordProvider +import com.android.settings.biometrics.fingerprint2.data.repository.DebuggingRepositoryImpl import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepositoryImpl +import com.android.settings.biometrics.fingerprint2.data.repository.UdfpsEnrollDebugRepositoryImpl import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractorImpl +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.EnrollStageInteractor +import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractorImpl +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.FoldStateInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractorImpl 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.PressToAuthInteractorImpl +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.model.Default +import com.android.settings.biometrics.fingerprint2.lib.model.Settings import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollConfirmationV2Fragment import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollEnrollingV2Fragment -import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollFindSensorV2Fragment import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollIntroV2Fragment +import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education.RfpsEnrollFindSensorFragment +import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education.SfpsEnrollFindSensorFragment +import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education.UdfpsEnrollFindSensorFragment +import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.util.toFingerprintEnrollOptions import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.fragment.RFPSEnrollFragment import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.fragment.UdfpsEnrollFragment @@ -77,6 +93,7 @@ import com.android.settings.flags.Flags import com.android.settings.password.ChooseLockGeneric import com.android.settings.password.ChooseLockSettingsHelper import com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE +import com.android.settingslib.display.DisplayDensityUtils import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.google.android.setupcompat.util.WizardManagerHelper import com.google.android.setupdesign.util.ThemeHelper @@ -95,14 +112,17 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() { private lateinit var navigationViewModel: FingerprintNavigationViewModel private lateinit var gatekeeperViewModel: FingerprintGatekeeperViewModel private lateinit var fingerprintEnrollViewModel: FingerprintEnrollViewModel + private lateinit var vibrationInteractor: VibrationInteractor private lateinit var foldStateInteractor: FoldStateInteractor private lateinit var orientationInteractor: OrientationInteractor + private lateinit var displayDensityInteractor: DisplayDensityInteractor private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel private lateinit var backgroundViewModel: BackgroundViewModel private lateinit var fingerprintFlowViewModel: FingerprintFlowViewModel private lateinit var fingerprintEnrollConfirmationViewModel: FingerprintEnrollConfirmationViewModel private lateinit var udfpsViewModel: UdfpsViewModel + private lateinit var enrollStageInteractor: EnrollStageInteractor private val coroutineDispatcher = Dispatchers.Default /** Result listener for ChooseLock activity flow. */ @@ -135,6 +155,12 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() { override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) foldStateInteractor.onConfigurationChange(newConfig) + val displayDensityUtils = DisplayDensityUtils(applicationContext) + val currIndex = displayDensityUtils.currentIndexForDefaultDisplay + displayDensityInteractor.updateFontScale(resources.configuration.fontScale) + displayDensityInteractor.updateDisplayDensity( + displayDensityUtils.defaultDisplayDensityValues[currIndex] + ) } private fun onConfirmDevice(resultCode: Int, data: Intent?) { @@ -193,10 +219,43 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() { fingerprintFlowViewModel = ViewModelProvider(this, FingerprintFlowViewModel.FingerprintFlowViewModelFactory(enrollType))[ FingerprintFlowViewModel::class.java] + val displayDensityUtils = DisplayDensityUtils(context) + val currIndex = displayDensityUtils.currentIndexForDefaultDisplay + val defaultDisplayDensity = displayDensityUtils.defaultDensityForDefaultDisplay + displayDensityInteractor = + DisplayDensityInteractorImpl( + resources.configuration.fontScale, + displayDensityUtils.defaultDisplayDensityValues[currIndex], + defaultDisplayDensity, + lifecycleScope, + ) + + val debuggingRepo = DebuggingRepositoryImpl() + val debuggingInteractor = DebuggingInteractorImpl(debuggingRepo) + val udfpsEnrollDebugRepositoryImpl = UdfpsEnrollDebugRepositoryImpl() val fingerprintSensorRepo = - FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, lifecycleScope) - val pressToAuthInteractor = PressToAuthInteractorImpl(context, backgroundDispatcher) + if (debuggingRepo.isUdfpsEnrollmentDebuggingEnabled()) udfpsEnrollDebugRepositoryImpl + else FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, lifecycleScope) + + if (intent.getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1) === -1) { + val isSuw: Boolean = WizardManagerHelper.isAnySetupWizard(intent) + intent.putExtra( + BiometricUtils.EXTRA_ENROLL_REASON, + if (isSuw) FingerprintEnrollOptions.ENROLL_REASON_SUW + else FingerprintEnrollOptions.ENROLL_REASON_SETTINGS, + ) + } + + val fingerprintEnrollStateRepository = + if (debuggingRepo.isUdfpsEnrollmentDebuggingEnabled()) udfpsEnrollDebugRepositoryImpl + else + FingerprintEnrollInteractorImpl( + context.applicationContext, + intent.toFingerprintEnrollOptions(), + fingerprintManager, + Settings, + ) val fingerprintManagerInteractor = FingerprintManagerInteractorImpl( @@ -205,12 +264,10 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() { fingerprintManager, fingerprintSensorRepo, GatekeeperPasswordProvider(LockPatternUtils(context)), - pressToAuthInteractor, - enrollType, - getIntent(), + fingerprintEnrollStateRepository, ) - var challenge: Long? = intent.getExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE) as Long? + var challenge = intent.getExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE) as Long? val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN) val gatekeeperInfo = FingerprintGatekeeperViewModel.toGateKeeperInfo(challenge, token) @@ -256,6 +313,8 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() { foldStateInteractor.onConfigurationChange(resources.configuration) orientationInteractor = OrientationInteractorImpl(context, lifecycleScope) + vibrationInteractor = + VibrationInteractorImpl(context.getSystemService(Vibrator::class.java)!!, context) // Initialize FingerprintViewModel fingerprintEnrollViewModel = @@ -309,10 +368,23 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() { ), )[RFPSViewModel::class.java] + enrollStageInteractor = EnrollStageInteractorImpl() + udfpsViewModel = ViewModelProvider( this, - UdfpsViewModel.UdfpsEnrollmentFactory(), + UdfpsViewModel.UdfpsEnrollmentFactory( + vibrationInteractor, + displayDensityInteractor, + navigationViewModel, + debuggingInteractor, + fingerprintEnrollEnrollingViewModel, + udfpsEnrollDebugRepositoryImpl, + enrollStageInteractor, + orientationInteractor, + backgroundViewModel, + fingerprintSensorRepo, + ), )[UdfpsViewModel::class.java] fingerprintEnrollConfirmationViewModel = @@ -348,7 +420,12 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() { when (step) { Confirmation -> FingerprintEnrollConfirmationV2Fragment() is Education -> { - FingerprintEnrollFindSensorV2Fragment(step.sensor.sensorType) + when (step.sensor.sensorType) { + FingerprintSensorType.REAR -> RfpsEnrollFindSensorFragment() + FingerprintSensorType.UDFPS_OPTICAL, + FingerprintSensorType.UDFPS_ULTRASONIC -> UdfpsEnrollFindSensorFragment() + else -> SfpsEnrollFindSensorFragment() + } } is Enrollment -> { when (step.sensor.sensorType) { @@ -370,7 +447,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() { supportFragmentManager .beginTransaction() .setReorderingAllowed(true) - .add(R.id.fragment_container_view, theClass, null) + .add(R.id.fragment_container_view, theClass::class.java, null) .commit() navigationViewModel.update( FingerprintAction.TRANSITION_FINISHED, @@ -386,7 +463,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() { navigationViewModel.shouldFinish.filterNotNull().collect { Log.d(TAG, "FingerprintSettingsNav.finishing($it)") if (it.result != null) { - finishActivity(it.result as Int) + finishActivity(it.result) } else { finish() } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/RfpsEnrollFindSensorFragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/RfpsEnrollFindSensorFragment.kt new file mode 100644 index 00000000000..5ef17708d05 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/RfpsEnrollFindSensorFragment.kt @@ -0,0 +1,132 @@ +/* + * 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.ui.enrollment.fragment.education + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.VisibleForTesting +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import com.android.settings.R +import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog +import com.android.settings.biometrics.fingerprint.FingerprintFindSensorAnimation +import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel +import com.google.android.setupcompat.template.FooterBarMixin +import com.google.android.setupcompat.template.FooterButton +import com.google.android.setupdesign.GlifLayout +import kotlinx.coroutines.launch + +/** + * A fragment that is used to educate the user about the rear fingerprint sensor on this device. + * + * The main goals of this page are + * 1. Inform the user where the fingerprint sensor is on their device + * 2. Explain to the user how the enrollment process shown by [FingerprintEnrollEnrollingV2Fragment] + * will work. + */ +class RfpsEnrollFindSensorFragment() : Fragment() { + /** Used for testing purposes */ + private var factory: ViewModelProvider.Factory? = null + + @VisibleForTesting + constructor(theFactory: ViewModelProvider.Factory) : this() { + factory = theFactory + } + + private val viewModelProvider: ViewModelProvider by lazy { + if (factory != null) { + ViewModelProvider(requireActivity(), factory!!) + } else { + ViewModelProvider(requireActivity()) + } + } + + private var animation: FingerprintFindSensorAnimation? = null + + private val viewModel: FingerprintEnrollFindSensorViewModel by lazy { + viewModelProvider[FingerprintEnrollFindSensorViewModel::class.java] + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View? { + val view = + inflater.inflate(R.layout.fingerprint_v2_enroll_find_sensor, container, false)!! as GlifLayout + view.setHeaderText(R.string.security_settings_fingerprint_enroll_find_sensor_title) + view.setDescriptionText(R.string.security_settings_fingerprint_enroll_find_sensor_message) + + // Set up footer bar + val footerBarMixin = view.getMixin(FooterBarMixin::class.java) + setupSecondaryButton(footerBarMixin) + lifecycleScope.launch { + viewModel.showPrimaryButton.collect { setupPrimaryButton(footerBarMixin) } + } + + lifecycleScope.launch { + viewModel.showRfpsAnimation.collect { + animation = view.findViewById(R.id.fingerprint_sensor_location_animation) + animation!!.startAnimation() + } + } + + lifecycleScope.launch { + viewModel.showErrorDialog.collect { (errMsgId, isSetup) -> + // TODO: Covert error dialog kotlin as well + FingerprintErrorDialog.showErrorDialog(requireActivity(), errMsgId, isSetup) + } + } + return view + } + + override fun onDestroy() { + animation?.stopAnimation() + super.onDestroy() + } + + private fun setupSecondaryButton(footerBarMixin: FooterBarMixin) { + footerBarMixin.secondaryButton = + FooterButton.Builder(requireActivity()) + .setText(R.string.security_settings_fingerprint_enroll_enrolling_skip) + .setListener { viewModel.secondaryButtonClicked() } + .setButtonType(FooterButton.ButtonType.SKIP) + .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary) + .build() + } + + private fun setupPrimaryButton(footerBarMixin: FooterBarMixin) { + footerBarMixin.primaryButton = + FooterButton.Builder(requireActivity()) + .setText(R.string.security_settings_udfps_enroll_find_sensor_start_button) + .setListener { + Log.d(TAG, "onStartButtonClick") + viewModel.proceedToEnrolling() + } + .setButtonType(FooterButton.ButtonType.NEXT) + .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary) + .build() + } + + companion object { + private const val TAG = "RfpsEnrollFindSensor" + } +} diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/SfpsEnrollFindSensorFragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/SfpsEnrollFindSensorFragment.kt new file mode 100644 index 00000000000..584824d4573 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/SfpsEnrollFindSensorFragment.kt @@ -0,0 +1,162 @@ +/* + * 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.ui.enrollment.fragment.education + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.Surface +import android.view.View +import android.view.ViewGroup +import androidx.annotation.VisibleForTesting +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import com.airbnb.lottie.LottieAnimationView +import com.android.settings.R +import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog +import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel +import com.google.android.setupcompat.template.FooterBarMixin +import com.google.android.setupcompat.template.FooterButton +import com.google.android.setupdesign.GlifLayout +import kotlinx.coroutines.launch + +/** + * A fragment that is used to educate the user about the side fingerprint sensor on this device. + * + * The main goals of this page are + * 1. Inform the user where the fingerprint sensor is on their device + * 2. Explain to the user how the enrollment process shown by [FingerprintEnrollEnrollingV2Fragment] + * will work. + */ +class SfpsEnrollFindSensorFragment() : Fragment() { + /** Used for testing purposes */ + private var factory: ViewModelProvider.Factory? = null + + @VisibleForTesting + constructor(theFactory: ViewModelProvider.Factory) : this() { + factory = theFactory + } + + private val viewModelProvider: ViewModelProvider by lazy { + if (factory != null) { + ViewModelProvider(requireActivity(), factory!!) + } else { + ViewModelProvider(requireActivity()) + } + } + + private val viewModel: FingerprintEnrollFindSensorViewModel by lazy { + viewModelProvider[FingerprintEnrollFindSensorViewModel::class.java] + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View? { + val view = + inflater.inflate(R.layout.sfps_enroll_find_sensor_layout, container, false)!! as GlifLayout + view.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title) + view.setDescriptionText(R.string.security_settings_sfps_enroll_find_sensor_message) + + // Set up footer bar + val footerBarMixin = view.getMixin(FooterBarMixin::class.java) + setupSecondaryButton(footerBarMixin) + + // Set up lottie + lifecycleScope.launch { + viewModel.sfpsLottieInfo.collect { (isFolded, rotation) -> + setupLottie(view, getSfpsIllustrationLottieAnimation(isFolded, rotation)) + } + } + + lifecycleScope.launch { + viewModel.showPrimaryButton.collect { setupPrimaryButton(footerBarMixin) } + } + + lifecycleScope.launch { + viewModel.showErrorDialog.collect { (errMsgId, isSetup) -> + // TODO: Covert error dialog kotlin as well + FingerprintErrorDialog.showErrorDialog(requireActivity(), errMsgId, isSetup) + } + } + return view + } + + private fun setupSecondaryButton(footerBarMixin: FooterBarMixin) { + footerBarMixin.secondaryButton = + FooterButton.Builder(requireActivity()) + .setText(R.string.security_settings_fingerprint_enroll_enrolling_skip) + .setListener { viewModel.secondaryButtonClicked() } + .setButtonType(FooterButton.ButtonType.SKIP) + .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary) + .build() + } + + private fun setupPrimaryButton(footerBarMixin: FooterBarMixin) { + footerBarMixin.primaryButton = + FooterButton.Builder(requireActivity()) + .setText(R.string.security_settings_udfps_enroll_find_sensor_start_button) + .setListener { + Log.d(TAG, "onStartButtonClick") + viewModel.proceedToEnrolling() + } + .setButtonType(FooterButton.ButtonType.NEXT) + .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary) + .build() + } + + private fun getSfpsIllustrationLottieAnimation(isFolded: Boolean, rotation: Int): Int { + val animation: Int + when (rotation) { + Surface.ROTATION_90 -> + animation = + (if (isFolded) R.raw.fingerprint_edu_lottie_folded_top_left + else R.raw.fingerprint_edu_lottie_portrait_top_left) + Surface.ROTATION_180 -> + animation = + (if (isFolded) R.raw.fingerprint_edu_lottie_folded_bottom_left + else R.raw.fingerprint_edu_lottie_landscape_bottom_left) + Surface.ROTATION_270 -> + animation = + (if (isFolded) R.raw.fingerprint_edu_lottie_folded_bottom_right + else R.raw.fingerprint_edu_lottie_portrait_bottom_right) + else -> + animation = + (if (isFolded) R.raw.fingerprint_edu_lottie_folded_top_right + else R.raw.fingerprint_edu_lottie_landscape_top_right) + } + return animation + } + + private fun setupLottie( + view: View, + lottieAnimation: Int, + lottieClickListener: View.OnClickListener? = null, + ) { + val illustrationLottie: LottieAnimationView? = view.findViewById(R.id.illustration_lottie) + illustrationLottie?.setAnimation(lottieAnimation) + illustrationLottie?.playAnimation() + illustrationLottie?.setOnClickListener(lottieClickListener) + illustrationLottie?.visibility = View.VISIBLE + } + + companion object { + private const val TAG = "SfpsEnrollFindSensor" + } +} diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/UdfpsEnrollFindSensorFragment.kt similarity index 66% rename from src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt rename to src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/UdfpsEnrollFindSensorFragment.kt index 2b1ff9bf085..923a309cbac 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/UdfpsEnrollFindSensorFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment +package com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education import android.os.Bundle import android.util.Log @@ -29,36 +29,27 @@ import androidx.lifecycle.lifecycleScope import com.airbnb.lottie.LottieAnimationView import com.android.settings.R import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog -import com.android.settings.biometrics.fingerprint.FingerprintFindSensorAnimation import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel -import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.google.android.setupcompat.template.FooterBarMixin import com.google.android.setupcompat.template.FooterButton import com.google.android.setupdesign.GlifLayout import kotlinx.coroutines.launch -private const val TAG = "FingerprintEnrollFindSensorV2Fragment" - /** - * A fragment that is used to educate the user about the fingerprint sensor on this device. - * - * If the sensor is not a udfps sensor, this fragment listens to fingerprint enrollment for - * proceeding to the enroll enrolling. + * A fragment that is used to educate the user about the under display fingerprint sensor on this + * device. * * The main goals of this page are * 1. Inform the user where the fingerprint sensor is on their device * 2. Explain to the user how the enrollment process shown by [FingerprintEnrollEnrollingV2Fragment] * will work. */ -class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorType) : Fragment() { +class UdfpsEnrollFindSensorFragment() : Fragment() { /** Used for testing purposes */ private var factory: ViewModelProvider.Factory? = null @VisibleForTesting - constructor( - sensorType: FingerprintSensorType, - theFactory: ViewModelProvider.Factory, - ) : this(sensorType) { + constructor(theFactory: ViewModelProvider.Factory) : this() { factory = theFactory } @@ -70,10 +61,6 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp } } - // This is only for non-udfps or non-sfps sensor. For udfps and sfps, we show lottie. - private var animation: FingerprintFindSensorAnimation? = null - - private var contentLayoutId: Int = -1 private val viewModel: FingerprintEnrollFindSensorViewModel by lazy { viewModelProvider[FingerprintEnrollFindSensorViewModel::class.java] } @@ -83,31 +70,18 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp container: ViewGroup?, savedInstanceState: Bundle?, ): View? { - - contentLayoutId = - when (sensorType) { - FingerprintSensorType.UDFPS_OPTICAL, - FingerprintSensorType.UDFPS_ULTRASONIC -> R.layout.udfps_enroll_find_sensor_layout - FingerprintSensorType.POWER_BUTTON -> R.layout.sfps_enroll_find_sensor_layout - else -> R.layout.fingerprint_v2_enroll_find_sensor - } - - val view = inflater.inflate(contentLayoutId, container, false)!! as GlifLayout - setTexts(sensorType, view) + val view = + inflater.inflate(R.layout.udfps_enroll_find_sensor_layout, container, false)!! as GlifLayout + view.setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title) + view.setDescriptionText(R.string.security_settings_udfps_enroll_find_sensor_message) // Set up footer bar val footerBarMixin = view.getMixin(FooterBarMixin::class.java) setupSecondaryButton(footerBarMixin) + lifecycleScope.launch { viewModel.showPrimaryButton.collect { setupPrimaryButton(footerBarMixin) } } - - // Set up lottie or animation - lifecycleScope.launch { - viewModel.sfpsLottieInfo.collect { (isFolded, rotation) -> - setupLottie(view, getSfpsIllustrationLottieAnimation(isFolded, rotation)) - } - } lifecycleScope.launch { viewModel.udfpsLottieInfo.collect { isAccessibilityEnabled -> val lottieAnimation = @@ -115,12 +89,6 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp setupLottie(view, lottieAnimation) { viewModel.proceedToEnrolling() } } } - lifecycleScope.launch { - viewModel.showRfpsAnimation.collect { - animation = view.findViewById(R.id.fingerprint_sensor_location_animation) - animation!!.startAnimation() - } - } lifecycleScope.launch { viewModel.showErrorDialog.collect { (errMsgId, isSetup) -> @@ -131,11 +99,6 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp return view } - override fun onDestroy() { - animation?.stopAnimation() - super.onDestroy() - } - private fun setupSecondaryButton(footerBarMixin: FooterBarMixin) { footerBarMixin.secondaryButton = FooterButton.Builder(requireActivity()) @@ -159,36 +122,6 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp .build() } - private fun setupLottie( - view: View, - lottieAnimation: Int, - lottieClickListener: View.OnClickListener? = null, - ) { - val illustrationLottie: LottieAnimationView? = view.findViewById(R.id.illustration_lottie) - illustrationLottie?.setAnimation(lottieAnimation) - illustrationLottie?.playAnimation() - illustrationLottie?.setOnClickListener(lottieClickListener) - illustrationLottie?.visibility = View.VISIBLE - } - - private fun setTexts(sensorType: FingerprintSensorType?, view: GlifLayout) { - when (sensorType) { - FingerprintSensorType.UDFPS_OPTICAL, - FingerprintSensorType.UDFPS_ULTRASONIC -> { - view.setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title) - view.setDescriptionText(R.string.security_settings_udfps_enroll_find_sensor_message) - } - FingerprintSensorType.POWER_BUTTON -> { - view.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title) - view.setDescriptionText(R.string.security_settings_sfps_enroll_find_sensor_message) - } - else -> { - view.setHeaderText(R.string.security_settings_fingerprint_enroll_find_sensor_title) - view.setDescriptionText(R.string.security_settings_fingerprint_enroll_find_sensor_message) - } - } - } - private fun getSfpsIllustrationLottieAnimation(isFolded: Boolean, rotation: Int): Int { val animation: Int when (rotation) { @@ -211,4 +144,20 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp } return animation } + + private fun setupLottie( + view: View, + lottieAnimation: Int, + lottieClickListener: View.OnClickListener? = null, + ) { + val illustrationLottie: LottieAnimationView? = view.findViewById(R.id.illustration_lottie) + illustrationLottie?.setAnimation(lottieAnimation) + illustrationLottie?.playAnimation() + illustrationLottie?.setOnClickListener(lottieClickListener) + illustrationLottie?.visibility = View.VISIBLE + } + + companion object { + private const val TAG = "UdfpsEnrollFindSensor" + } } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/common/util/FingerprintOptionUtil.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/common/util/FingerprintOptionUtil.kt new file mode 100644 index 00000000000..7742fd97fc2 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/common/util/FingerprintOptionUtil.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.ui.enrollment.modules.enrolling.common.util + +import android.content.Intent +import android.hardware.fingerprint.FingerprintEnrollOptions +import com.android.settings.biometrics.BiometricUtils + +fun Intent.toFingerprintEnrollOptions(): FingerprintEnrollOptions { + val reason: Int = this.getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1) + val builder: FingerprintEnrollOptions.Builder = FingerprintEnrollOptions.Builder() + builder.setEnrollReason(FingerprintEnrollOptions.ENROLL_REASON_UNKNOWN) + if (reason != -1) { + builder.setEnrollReason(reason) + } + return builder.build() +} diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/FingerprintErrorDialog.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/common/widget/FingerprintErrorDialog.kt similarity index 96% rename from src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/FingerprintErrorDialog.kt rename to src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/common/widget/FingerprintErrorDialog.kt index 9c0040b8ded..dc4842ab700 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/FingerprintErrorDialog.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/common/widget/FingerprintErrorDialog.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget +package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.widget import android.app.AlertDialog import android.app.Dialog @@ -29,8 +29,6 @@ import com.android.settings.core.instrumentation.InstrumentedDialogFragment import kotlin.coroutines.resume import kotlinx.coroutines.suspendCancellableCoroutine -private const val TAG = "FingerprintErrorDialog" - /** A Dialog used for fingerprint enrollment when an error occurs. */ class FingerprintErrorDialog : InstrumentedDialogFragment() { private lateinit var onContinue: DialogInterface.OnClickListener @@ -82,6 +80,7 @@ class FingerprintErrorDialog : InstrumentedDialogFragment() { } companion object { + private const val TAG = "FingerprintErrorDialog" private const val KEY_MESSAGE = "fingerprint_message" private const val KEY_TITLE = "fingerprint_title" private const val KEY_SHOULD_TRY_AGAIN = "should_try_again" diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/fragment/RFPSEnrollFragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/fragment/RFPSEnrollFragment.kt index f6917f3bf0e..0ec0bdd3c26 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/fragment/RFPSEnrollFragment.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/fragment/RFPSEnrollFragment.kt @@ -34,9 +34,9 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.android.settings.R import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState +import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.widget.FingerprintErrorDialog import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSIconTouchViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSViewModel -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.FingerprintErrorDialog import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.IconTouchDialog import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.RFPSProgressBar import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel 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 6ee57099ea2..06450814103 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 @@ -43,7 +43,7 @@ class RFPSViewModel( orientationInteractor: OrientationInteractor, ) : ViewModel() { - private val _textViewIsVisible = MutableStateFlow(false) + private val _textViewIsVisible = MutableStateFlow(false) /** Value to indicate if the text view is visible or not */ val textViewIsVisible: Flow = _textViewIsVisible.asStateFlow() @@ -52,7 +52,7 @@ class RFPSViewModel( /** Indicates if the icon should be animating or not */ val shouldAnimateIcon = _shouldAnimateIcon - private var enrollFlow: Flow = fingerprintEnrollViewModel.enrollFLow + private var enrollFlow: Flow = fingerprintEnrollViewModel.enrollFlow /** * Enroll progress message with a replay of size 1 allowing for new subscribers to get the most @@ -142,7 +142,7 @@ class RFPSViewModel( _textViewIsVisible.update { false } _shouldAnimateIcon = fingerprintEnrollViewModel.enrollFlowShouldBeRunning /** Indicates if the icon should be animating or not */ - enrollFlow = fingerprintEnrollViewModel.enrollFLow + enrollFlow = fingerprintEnrollViewModel.enrollFlow } class RFPSViewModelFactory( diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/fragment/UdfpsEnrollFragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/fragment/UdfpsEnrollFragment.kt index d1abcd0225c..c96a1b45302 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/fragment/UdfpsEnrollFragment.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/fragment/UdfpsEnrollFragment.kt @@ -18,6 +18,8 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrol import android.os.Bundle import android.util.Log +import android.view.MotionEvent +import android.view.MotionEvent.ACTION_HOVER_MOVE import android.view.View import android.view.WindowManager import android.widget.TextView @@ -30,10 +32,12 @@ import androidx.lifecycle.repeatOnLifecycle import com.airbnb.lottie.LottieAnimationView import com.airbnb.lottie.LottieCompositionFactory import com.android.settings.R +import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState +import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel +import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.widget.FingerprintErrorDialog import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.DescriptionText -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.HeaderText import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.EducationAnimationModel -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.StageViewModel +import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.HeaderText import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.widget.UdfpsEnrollViewV2 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep @@ -47,6 +51,7 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro private var factory: ViewModelProvider.Factory? = null private val viewModel: UdfpsViewModel by lazy { viewModelProvider[UdfpsViewModel::class.java] } private lateinit var udfpsEnrollView: UdfpsEnrollViewV2 + private lateinit var lottie: LottieAnimationView private val viewModelProvider: ViewModelProvider by lazy { if (factory != null) { @@ -63,7 +68,8 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val illustrationLottie: LottieAnimationView = view.findViewById(R.id.illustration_lottie)!! + val fragment = this + lottie = view.findViewById(R.id.illustration_lottie)!! udfpsEnrollView = view.findViewById(R.id.udfps_animation_view)!! val titleTextView = view.findViewById(R.id.title)!! val descriptionTextView = view.findViewById(R.id.description)!! @@ -79,6 +85,11 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.RESUMED) { + launch { + viewModel.sensorLocation.collect { sensor -> + udfpsEnrollView.setSensorRect(sensor.sensorBounds, sensor.sensorType) + } + } viewLifecycleOwner.lifecycleScope.launch { viewModel.headerText.collect { titleTextView.setText(it.toResource()) } } @@ -92,35 +103,59 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro } } } - viewLifecycleOwner.lifecycleScope.launch { - viewModel.sensorLocation.collect { rect -> udfpsEnrollView.setSensorRect(rect) } - } - - viewLifecycleOwner.lifecycleScope.launch { - viewModel.accessibilityEnabled.collect { isEnabled -> udfpsEnrollView.setAccessibilityEnabled(isEnabled) } + viewModel.shouldShowLottie.collect { + lottie.visibility = if (it) View.VISIBLE else View.GONE + } } viewLifecycleOwner.lifecycleScope.launch { viewModel.lottie.collect { lottieModel -> + if (lottie.visibility == View.GONE) { + return@collect + } val resource = lottieModel.toResource() if (resource != null) { LottieCompositionFactory.fromRawRes(requireContext(), resource).addListener { comp -> comp?.let { composition -> - illustrationLottie.setComposition(composition) - illustrationLottie.visibility = View.VISIBLE - illustrationLottie.playAnimation() + lottie.setComposition(composition) + lottie.visibility = View.VISIBLE + lottie.playAnimation() } } } else { - illustrationLottie.visibility = View.INVISIBLE + lottie.visibility = View.INVISIBLE } } } + viewLifecycleOwner.lifecycleScope.launch { - viewModel.udfpsEvent.collect { - Log.d(TAG, "EnrollEvent $it") - udfpsEnrollView.onUdfpsEvent(it) } + repeatOnLifecycle(Lifecycle.State.DESTROYED) { viewModel.stopEnrollment() } + } + + viewLifecycleOwner.lifecycleScope.launch { + viewModel.accessibilityEnabled.collect { enabled -> + udfpsEnrollView.setAccessibilityEnabled(enabled) + } + } + + viewLifecycleOwner.lifecycleScope.launch { + viewModel.enrollState.collect { + Log.d(TAG, "EnrollEvent $it") + if (it is FingerEnrollState.EnrollError) { + try { + FingerprintErrorDialog.showInstance(it, fragment) + } catch (exception: Exception) { + Log.e(TAG, "Exception occurred $exception") + } + } else { + udfpsEnrollView.onUdfpsEvent(it) + } + } + } + + viewLifecycleOwner.lifecycleScope.launch { + viewModel.progressSaved.collect { udfpsEnrollView.onEnrollProgressSaved(it) } } viewLifecycleOwner.lifecycleScope.launch { @@ -128,6 +163,15 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro } } } + + viewLifecycleOwner.lifecycleScope.launch { + viewModel.touchExplorationDebug.collect { + udfpsEnrollView.sendDebugTouchExplorationEvent( + MotionEvent.obtain(100, 100, ACTION_HOVER_MOVE, it.x.toFloat(), it.y.toFloat(), 0) + ) + } + } + viewModel.readyForEnrollment() } private fun HeaderText.toResource(): Int { diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/DescriptionText.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/DescriptionText.kt index 192a7871e78..175fea0ac13 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/DescriptionText.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/DescriptionText.kt @@ -16,6 +16,8 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel +import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel + /** Represents the description text for UDFPS enrollment */ data class DescriptionText( val isSuw: Boolean, diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/EducationAnimationModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/EducationAnimationModel.kt index cf125c37411..a274179e303 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/EducationAnimationModel.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/EducationAnimationModel.kt @@ -16,6 +16,8 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel +import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel + /** Represents the lottie for UDFPS enrollment */ data class EducationAnimationModel( val isSuw: Boolean, diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/HeaderText.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/HeaderText.kt index 9cfcddc8d4d..c565f35487c 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/HeaderText.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/HeaderText.kt @@ -16,6 +16,8 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel +import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel + /** Represents the header text for UDFPS enrollment */ data class HeaderText( val isSuw: Boolean, diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsEnrollEvent.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsEnrollEvent.kt deleted file mode 100644 index e349cebd2eb..00000000000 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsEnrollEvent.kt +++ /dev/null @@ -1,41 +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.ui.enrollment.modules.enrolling.udfps.ui.viewmodel - -/** A class indicating a udfps enroll event occurred. */ -sealed class UdfpsEnrollEvent - -/** Describes how many [remainingSteps] and how many [totalSteps] are left in udfps enrollment. */ -data class UdfpsProgress(val remainingSteps: Int, val totalSteps: Int) : UdfpsEnrollEvent() - -/** Indicates a help event has been sent by enrollment */ -data class UdfpsHelp(val helpMsgId: Int, val helpString: String) : UdfpsEnrollEvent() - -/** Indicates a error event has been sent by enrollment */ -data class UdfpsError(val errMsgId: Int, val errString: String) : UdfpsEnrollEvent() - -/** Indicates an acquired event has occurred */ -data class Acquired(val acquiredGood: Boolean) : UdfpsEnrollEvent() - -/** Indicates a pointer down event has occurred */ -data object PointerDown : UdfpsEnrollEvent() - -/** Indicates a pointer up event has occurred */ -data object PointerUp : UdfpsEnrollEvent() - -/** Indicates the overlay has shown */ -data object OverlayShown : UdfpsEnrollEvent() 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 7d43e1b866b..37822378f21 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 @@ -16,167 +16,284 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel -import android.graphics.Rect +import android.graphics.Point +import android.view.Surface import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository +import com.android.settings.biometrics.fingerprint2.data.repository.SimulatedTouchEventsRepository +import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractor +import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractor +import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractor +import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintVibrationEffects +import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor +import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor +import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState +import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel +import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel +import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintAction +import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay +import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationViewModel import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.launch /** ViewModel used to drive UDFPS Enrollment through [UdfpsEnrollFragment] */ -class UdfpsViewModel() : ViewModel() { +class UdfpsViewModel( + val vibrationInteractor: VibrationInteractor, + displayDensityInteractor: DisplayDensityInteractor, + val navigationViewModel: FingerprintNavigationViewModel, + debuggingInteractor: DebuggingInteractor, + val fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel, + simulatedTouchEventsDebugRepository: SimulatedTouchEventsRepository, + enrollStageInteractor: EnrollStageInteractor, + orientationInteractor: OrientationInteractor, + backgroundViewModel: BackgroundViewModel, + sensorRepository: FingerprintSensorRepository, +) : ViewModel() { private val isSetupWizard = flowOf(false) - /** Indicates which Enrollment stage we are currently in. */ - private val sensorLocationInternal = Pair(540, 1713) - private val sensorRadius = 100 - private val sensorRect = - Rect( - this.sensorLocationInternal.first - sensorRadius, - this.sensorLocationInternal.second - sensorRadius, - this.sensorLocationInternal.first + sensorRadius, - this.sensorLocationInternal.second + sensorRadius, - ) - - private val stageThresholds = flowOf(listOf(.25, .5, .75, .875)) - - /** Indicates if accessibility is enabled */ - val accessibilityEnabled = flowOf(false) - - /** Indicates the locates of the fingerprint sensor. */ - val sensorLocation: Flow = flowOf(sensorRect) - - /** This is currently not hooked up to fingerprint manager, and is being fed mock events. */ - val udfpsEvent: Flow = - flow { - enrollEvents.forEach { events -> - events.forEach { event -> emit(event) } - delay(1000) - } - } - .flowOn(Dispatchers.IO) - - /** Determines the current [StageViewModel] enrollment is in */ - val enrollStage: Flow = - combine(stageThresholds, udfpsEvent) { thresholds, event -> - if (event is UdfpsProgress) { - thresholdToStageMap(thresholds, event.totalSteps - event.remainingSteps, event.totalSteps) + private var _enrollState: Flow = + fingerprintEnrollEnrollingViewModel.enrollFlow + /** The current state of the enrollment. */ + var enrollState: Flow = + combine(fingerprintEnrollEnrollingViewModel.enrollFlowShouldBeRunning, _enrollState) { + shouldBeRunning, + state -> + if (shouldBeRunning) { + state } else { null } } .filterNotNull() + /** + * Forwards the property sensor information. This is typically used to recreate views that must be + * aligned with the sensor. + */ + val sensorLocation = sensorRepository.fingerprintSensor + + /** Indicates if accessibility is enabled */ + val accessibilityEnabled = flowOf(true).shareIn(viewModelScope, SharingStarted.Eagerly, 1) + + init { + viewModelScope.launch { + enrollState + .combine(accessibilityEnabled) { event, isEnabled -> Pair(event, isEnabled) } + .collect { + if ( + when (it.first) { + is FingerEnrollState.EnrollError -> true + is FingerEnrollState.EnrollHelp -> it.second + is FingerEnrollState.EnrollProgress -> true + else -> false + } + ) { + vibrate(it.first) + } + } + } + + viewModelScope.launch { + backgroundViewModel.background.filter { it }.collect { didGoToBackground() } + } + } + + /** + * This is the saved progress, this is for when views are recreated and need saved state for the + * first time. + */ + var progressSaved: Flow = + enrollState + .filterIsInstance() + .filterNotNull() + .shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1) + + /** This sends touch exploration events only used for debugging purposes. */ + val touchExplorationDebug: Flow = + debuggingInteractor.debuggingEnabled.combineTransform( + simulatedTouchEventsDebugRepository.touchExplorationDebug + ) { enabled, point -> + if (enabled) { + emit(point) + } + } + + /** Determines the current [StageViewModel] enrollment is in */ + val enrollStage: Flow = + combine(enrollStageInteractor.enrollStageThresholds, enrollState) { thresholds, event -> + if (event is FingerEnrollState.EnrollProgress) { + val progress = + (event.totalStepsRequired - event.remainingSteps).toFloat() / event.totalStepsRequired + var stageToReturn: StageViewModel = StageViewModel.Center + thresholds.forEach { (threshold, stage) -> + if (progress < threshold) { + return@forEach + } + stageToReturn = stage + } + stageToReturn + } else { + null + } + } + .filterNotNull() + .shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1) + + /** Indicates if we should show the lottie. */ + val shouldShowLottie: Flow = + combine( + displayDensityInteractor.displayDensity, + displayDensityInteractor.defaultDisplayDensity, + displayDensityInteractor.fontScale, + orientationInteractor.rotation, + ) { currDisplayDensity, defaultDisplayDensity, fontScale, rotation -> + val canShowLottieForRotation = + when (rotation) { + Surface.ROTATION_0 -> true + else -> false + } + + canShowLottieForRotation && + if (fontScale > 1.0f) { + false + } else { + defaultDisplayDensity == currDisplayDensity + } + } + .shareIn(viewModelScope, SharingStarted.Eagerly, 1) + /** The header text for UDFPS enrollment */ val headerText: Flow = combine(isSetupWizard, accessibilityEnabled, enrollStage) { isSuw, isAccessibility, stage -> - return@combine HeaderText(isSuw, isAccessibility, stage) - } + return@combine HeaderText(isSuw, isAccessibility, stage) + } + .shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1) private val shouldClearDescriptionText = enrollStage.map { it is StageViewModel.Unknown } /** The description text for UDFPS enrollment */ val descriptionText: Flow = combine(isSetupWizard, accessibilityEnabled, enrollStage, shouldClearDescriptionText) { - isSuw, - isAccessibility, - stage, - shouldClearText -> - if (shouldClearText) { - return@combine null - } else { - return@combine DescriptionText(isSuw, isAccessibility, stage) + isSuw, + isAccessibility, + stage, + shouldClearText -> + if (shouldClearText) { + return@combine null + } else { + return@combine DescriptionText(isSuw, isAccessibility, stage) + } } - } + .shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1) + + /** Indicates if the consumer is ready for enrollment */ + fun readyForEnrollment() { + fingerprintEnrollEnrollingViewModel.canEnroll() + } + + /** Indicates if enrollment should stop */ + fun stopEnrollment() { + fingerprintEnrollEnrollingViewModel.stopEnroll() + } + + /** Indicates the negative button has been clicked */ + fun negativeButtonClicked() { + doReset() + navigationViewModel.update( + FingerprintAction.NEGATIVE_BUTTON_PRESSED, + navStep, + "$TAG#negativeButtonClicked", + ) + } + + /** Indicates that an enrollment was completed */ + fun finishedSuccessfully() { + doReset() + navigationViewModel.update(FingerprintAction.NEXT, navStep, "${TAG}#progressFinished") + } + + /** Indicates that the application went to the background. */ + private fun didGoToBackground() { + navigationViewModel.update( + FingerprintAction.DID_GO_TO_BACKGROUND, + navStep, + "$TAG#didGoToBackground", + ) + stopEnrollment() + } + + private fun doReset() { + /** Indicates if the icon should be animating or not */ + _enrollState = fingerprintEnrollEnrollingViewModel.enrollFlow + } /** The lottie that should be shown for UDFPS Enrollment */ val lottie: Flow = combine(isSetupWizard, accessibilityEnabled, enrollStage) { isSuw, isAccessibility, stage -> - return@combine EducationAnimationModel(isSuw, isAccessibility, stage) - }.distinctUntilChanged() + return@combine EducationAnimationModel(isSuw, isAccessibility, stage) + } + .distinctUntilChanged() + .shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1) - class UdfpsEnrollmentFactory() : ViewModelProvider.Factory { + /** Indicates we should send a vibration event */ + private fun vibrate(event: FingerEnrollState) { + val vibrationEvent = + when (event) { + is FingerEnrollState.EnrollError -> FingerprintVibrationEffects.UdfpsError + is FingerEnrollState.EnrollHelp -> FingerprintVibrationEffects.UdfpsHelp + is FingerEnrollState.EnrollProgress -> FingerprintVibrationEffects.UdfpsSuccess + else -> FingerprintVibrationEffects.UdfpsError + } + vibrationInteractor.vibrate(vibrationEvent, "UdfpsEnrollFragment") + } + + class UdfpsEnrollmentFactory( + private val vibrationInteractor: VibrationInteractor, + private val displayDensityInteractor: DisplayDensityInteractor, + private val navigationViewModel: FingerprintNavigationViewModel, + private val debuggingInteractor: DebuggingInteractor, + private val fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel, + private val simulatedTouchEventsRepository: SimulatedTouchEventsRepository, + private val enrollStageInteractor: EnrollStageInteractor, + private val orientationInteractor: OrientationInteractor, + private val backgroundViewModel: BackgroundViewModel, + private val sensorRepository: FingerprintSensorRepository, + ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { - return UdfpsViewModel() as T + return UdfpsViewModel( + vibrationInteractor, + displayDensityInteractor, + navigationViewModel, + debuggingInteractor, + fingerprintEnrollEnrollingViewModel, + simulatedTouchEventsRepository, + enrollStageInteractor, + orientationInteractor, + backgroundViewModel, + sensorRepository, + ) + as T } } companion object { private val navStep = FingerprintNavigationStep.Enrollment::class private const val TAG = "UDFPSViewModel" - private val ENROLLMENT_STAGES_ORDERED = - listOf( - StageViewModel.Center, - StageViewModel.Guided, - StageViewModel.Fingertip, - StageViewModel.LeftEdge, - StageViewModel.RightEdge, - ) - - /** - * [thresholds] is a list of 4 numbers from [0,1] that separate enrollment into 5 stages. The - * stage is determined by mapping [thresholds] * [maxSteps] and finding where the [currentStep] - * is. - * - * Each number in the array should be strictly increasing such as [0.2, 0.5, 0.6, 0.8] - */ - private fun thresholdToStageMap( - thresholds: List, - currentStep: Int, - maxSteps: Int, - ): StageViewModel { - val stageIterator = ENROLLMENT_STAGES_ORDERED.iterator() - thresholds.forEach { - val thresholdLimit = it * maxSteps - val curr = stageIterator.next() - if (currentStep < thresholdLimit) { - return curr - } - } - return stageIterator.next() - } - - /** This will be removed */ - private val enrollEvents: List> = - listOf( - listOf(OverlayShown), - listOf(UdfpsHelp(1,"hi")), - listOf(UdfpsHelp(1,"hi")), - CreateProgress(15, 16), - listOf(UdfpsHelp(1,"hi")), - CreateProgress(14, 16), - listOf(PointerDown, UdfpsHelp(1,"hi"), PointerUp), - listOf(PointerDown, UdfpsHelp(1,"hi"), PointerUp), - CreateProgress(13, 16), - CreateProgress(12, 16), - CreateProgress(11, 16), - CreateProgress(10, 16), - CreateProgress(9, 16), - CreateProgress(8, 16), - CreateProgress(7, 16), - CreateProgress(6, 16), - CreateProgress(5, 16), - CreateProgress(4, 16), - CreateProgress(3, 16), - CreateProgress(2, 16), - CreateProgress(1, 16), - CreateProgress(0, 16), - ) - - /** This will be removed */ - private fun CreateProgress(remaining: Int, total: Int): List { - return listOf(PointerDown, Acquired(true), UdfpsProgress(remaining, total), PointerUp) - } } } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollHelperV2.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollHelperV2.kt index 5d4607c4f05..141924161d1 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollHelperV2.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollHelperV2.kt @@ -20,7 +20,7 @@ import android.content.Context import android.graphics.PointF import android.util.TypedValue import android.view.accessibility.AccessibilityManager -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.StageViewModel +import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel /** Keeps track of which guided enrollment point we should be using */ class UdfpsEnrollHelperV2(private val mContext: Context) { @@ -28,6 +28,7 @@ class UdfpsEnrollHelperV2(private val mContext: Context) { private var isGuidedEnrollment: Boolean = false private val accessibilityEnabled: Boolean private val guidedEnrollmentPoints: MutableList + /** The current index of [guidedEnrollmentPoints] for the guided enrollment. */ private var index = 0 init { @@ -76,7 +77,7 @@ class UdfpsEnrollHelperV2(private val mContext: Context) { if (accessibilityEnabled || !isGuidedEnrollment) { return null } - var scale = SCALE + val scale = SCALE val originalPoint = guidedEnrollmentPoints[index % guidedEnrollmentPoints.size] return PointF(originalPoint.x * scale, originalPoint.y * scale) } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollIconV2.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollIconV2.kt index 210cb2bda39..0d489954b80 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollIconV2.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollIconV2.kt @@ -37,7 +37,7 @@ import androidx.core.animation.addListener import androidx.core.graphics.toRect import androidx.core.graphics.toRectF import com.android.settings.R -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.StageViewModel +import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel import kotlin.math.sin /** @@ -45,6 +45,7 @@ import kotlin.math.sin * various stages of enrollment */ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeSet?) : Drawable() { + private var targetAnimationDuration: Long = TARGET_ANIM_DURATION_LONG private var targetAnimatorSet: AnimatorSet? = null private val movingTargetFpIcon: Drawable private val fingerprintDrawable: ShapeDrawable @@ -88,22 +89,25 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS it.recycle() } - sensorOutlinePaint = Paint(0 /* flags */).apply { - isAntiAlias = true - setColor(movingTargetFill) - style = Paint.Style.FILL - } + sensorOutlinePaint = + Paint(0 /* flags */).apply { + isAntiAlias = true + setColor(movingTargetFill) + style = Paint.Style.FILL + } - blueFill = Paint(0 /* flags */).apply { - isAntiAlias = true - setColor(movingTargetFill) - style = Paint.Style.FILL - } + blueFill = + Paint(0 /* flags */).apply { + isAntiAlias = true + setColor(movingTargetFill) + style = Paint.Style.FILL + } - movingTargetFpIcon = context.resources.getDrawable(R.drawable.ic_enrollment_fingerprint, null).apply { - setTint(enrollIconColor) - mutate() - } + movingTargetFpIcon = + context.resources.getDrawable(R.drawable.ic_enrollment_fingerprint, null).apply { + setTint(enrollIconColor) + mutate() + } fingerprintDrawable.setTint(enrollIconColor) setAlpha(255) @@ -140,7 +144,16 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS } /** Update the progress of the icon */ - fun onEnrollmentProgress(remaining: Int, totalSteps: Int) { + fun onEnrollmentProgress(remaining: Int, totalSteps: Int, isRecreating: Boolean = false) { + restoreAnimationTime() + // If we are restoring this view from a saved state, set animation duration to 0 to avoid + // animating progress that has already occurred. + if (isRecreating) { + setAnimationTimeToZero() + } else { + restoreAnimationTime() + } + helper.onEnrollmentProgress(remaining, totalSteps) val offset = helper.guidedEnrollmentLocation val currentBounds = getCurrLocation().toRect() @@ -149,10 +162,10 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS // offsets the initial sensor rect by a bit to get the user to move their finger a bit more. val targetRect = Rect(sensorRectBounds).toRectF() targetRect.offset(offset.x, offset.y) - var shouldAnimateMovement = + val shouldAnimateMovement = !currentBounds.equals(targetRect) && offset.x != 0f && offset.y != 0f if (shouldAnimateMovement) { - targetAnimatorSet?.let { it.cancel() } + targetAnimatorSet?.cancel() animateMovement(currentBounds, targetRect, true) } } else { @@ -186,7 +199,7 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS val currLocation = getCurrLocation() canvas.scale(currentScale, currentScale, currLocation.centerX(), currLocation.centerY()) - sensorRectBounds?.let { canvas.drawOval(currLocation, sensorOutlinePaint) } + canvas.drawOval(currLocation, sensorOutlinePaint) fingerprintDrawable.bounds = currLocation.toRect() fingerprintDrawable.draw(canvas) } @@ -234,6 +247,19 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS } } + /** + * This sets animation time to 0. This typically happens after an activity recreation, we don't + * want to re-animate the progress/success animation with the default timer + */ + private fun setAnimationTimeToZero() { + targetAnimationDuration = 0 + } + + /** This sets animation timers back to normal, this happens after we have */ + private fun restoreAnimationTime() { + targetAnimationDuration = TARGET_ANIM_DURATION_LONG + } + companion object { private const val TAG = "UdfpsEnrollDrawableV2" private const val DEFAULT_STROKE_WIDTH = 3f @@ -242,12 +268,13 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS private fun createUdfpsIcon(context: Context): ShapeDrawable { val fpPath = context.resources.getString(R.string.config_udfpsIcon) - val drawable = ShapeDrawable(PathShape(PathParser.createPathFromPathData(fpPath), 72f, 72f)).apply { - mutate() - paint.style = Paint.Style.STROKE - paint.strokeCap = Paint.Cap.ROUND - paint.strokeWidth = DEFAULT_STROKE_WIDTH - } + val drawable = + ShapeDrawable(PathShape(PathParser.createPathFromPathData(fpPath), 72f, 72f)).apply { + mutate() + paint.style = Paint.Style.STROKE + paint.strokeCap = Paint.Cap.ROUND + paint.strokeWidth = DEFAULT_STROKE_WIDTH + } return drawable } } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollProgressBarDrawableV2.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollProgressBarDrawableV2.kt index 5f7d2f992e5..8f0e84564a6 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollProgressBarDrawableV2.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollProgressBarDrawableV2.kt @@ -25,28 +25,28 @@ import android.graphics.Paint import android.graphics.PixelFormat import android.graphics.Rect import android.graphics.drawable.Drawable -import android.os.Process -import android.os.VibrationAttributes -import android.os.VibrationEffect -import android.os.Vibrator import android.util.AttributeSet import android.util.DisplayMetrics import android.view.animation.DecelerateInterpolator import android.view.animation.Interpolator +import android.view.animation.OvershootInterpolator import androidx.annotation.ColorInt import androidx.core.animation.doOnEnd import androidx.core.graphics.toRectF import com.android.internal.annotations.VisibleForTesting import com.android.settings.R +import kotlin.math.cos import kotlin.math.max +import kotlin.math.sin /** * UDFPS enrollment progress bar. This view is responsible for drawing the progress ring and its * fill around the center of the UDFPS sensor. */ -class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: AttributeSet?) : +class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: AttributeSet?) : Drawable() { private val sensorRect: Rect = Rect() + private var rotation: Int = 0 private val strokeWidthPx: Float @ColorInt private val progressColor: Int @@ -56,7 +56,6 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att private val backgroundPaint: Paint @VisibleForTesting val fillPaint: Paint - private val vibrator: Vibrator private var isAccessibilityEnabled: Boolean = false private var afterFirstTouch = false private var remainingSteps = 0 @@ -64,22 +63,27 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att private var progress = 0f private var progressAnimator: ValueAnimator? = null private val progressUpdateListener: AnimatorUpdateListener - private var showingHelp = false private var fillColorAnimator: ValueAnimator? = null private val fillColorUpdateListener: AnimatorUpdateListener private var backgroundColorAnimator: ValueAnimator? = null private val backgroundColorUpdateListener: AnimatorUpdateListener - private var complete = false private var movingTargetFill = 0 private var movingTargetFillError = 0 private var enrollProgressColor = 0 private var enrollProgressHelp = 0 private var enrollProgressHelpWithTalkback = 0 private val progressBarRadius: Int + private var checkMarkDrawable: Drawable + private var checkMarkAnimator: ValueAnimator? = null + + private var fillColorAnimationDuration = FILL_COLOR_ANIMATION_DURATION_MS + private var animateArcDuration = PROGRESS_ANIMATION_DURATION_MS + private var checkmarkAnimationDelayDuration = CHECKMARK_ANIMATION_DELAY_MS + private var checkmarkAnimationDuration = CHECKMARK_ANIMATION_DURATION_MS init { val ta = - mContext.obtainStyledAttributes( + context.obtainStyledAttributes( attrs, R.styleable.BiometricsEnrollView, R.attr.biometricsEnrollStyle, @@ -94,30 +98,33 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att enrollProgressHelpWithTalkback = ta.getColor(R.styleable.BiometricsEnrollView_biometricsEnrollProgressHelpWithTalkback, 0) ta.recycle() - val density = mContext.resources.displayMetrics.densityDpi.toFloat() + val density = context.resources.displayMetrics.densityDpi.toFloat() strokeWidthPx = STROKE_WIDTH_DP * (density / DisplayMetrics.DENSITY_DEFAULT) progressColor = enrollProgressColor onFirstBucketFailedColor = movingTargetFillError updateHelpColor() - backgroundPaint = Paint().apply { - strokeWidth = strokeWidthPx - setColor(movingTargetFill) - isAntiAlias = true - style = Paint.Style.STROKE - strokeCap = Paint.Cap.ROUND - } + backgroundPaint = + Paint().apply { + strokeWidth = strokeWidthPx + setColor(movingTargetFill) + isAntiAlias = true + style = Paint.Style.STROKE + strokeCap = Paint.Cap.ROUND + } + + checkMarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark)!! // Progress fill should *not* use the extracted system color. - fillPaint = Paint().apply { - strokeWidth = strokeWidthPx - setColor(progressColor) - isAntiAlias = true - style = Paint.Style.STROKE - strokeCap = Paint.Cap.ROUND - } - vibrator = mContext.getSystemService(Vibrator::class.java)!! + fillPaint = + Paint().apply { + strokeWidth = strokeWidthPx + setColor(progressColor) + isAntiAlias = true + style = Paint.Style.STROKE + strokeCap = Paint.Cap.ROUND + } - progressBarRadius = mContext.resources.getInteger(R.integer.config_udfpsEnrollProgressBar) + progressBarRadius = context.resources.getInteger(R.integer.config_udfpsEnrollProgressBar) progressUpdateListener = AnimatorUpdateListener { animation: ValueAnimator -> progress = animation.getAnimatedValue() as Float @@ -134,9 +141,10 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att } /** Indicates enrollment progress has occurred. */ - fun onEnrollmentProgress(remaining: Int, totalSteps: Int) { + fun onEnrollmentProgress(remaining: Int, totalSteps: Int, isRecreating: Boolean = false) { + afterFirstTouch = true - updateProgress(remaining, totalSteps) + updateProgress(remaining, totalSteps, isRecreating) } /** Indicates enrollment help has occurred. */ @@ -157,18 +165,12 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att canvas.save() // This takes the sensors bounding box and expands it by [progressBarRadius] in all directions - val sensorProgressRect = Rect(sensorRect) - sensorProgressRect.inset( - -progressBarRadius, - -progressBarRadius, - -progressBarRadius, - -progressBarRadius, - ) + val sensorProgressRect = getSensorProgressRect() // Rotate -90 degrees to make the progress start from the top right and not the bottom // right canvas.rotate( - -90f, + rotation - 90f, sensorProgressRect.centerX().toFloat(), sensorProgressRect.centerY().toFloat(), ) @@ -176,9 +178,9 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att // Draw the background color of the progress circle. canvas.drawArc( sensorProgressRect.toRectF(), - 0f /* startAngle */, - 360f /* sweepAngle */, - false /* useCenter */, + 0f, /* startAngle */ + 360f, /* sweepAngle */ + false, /* useCenter */ backgroundPaint, ) } @@ -186,13 +188,15 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att // Draw the filled portion of the progress circle. canvas.drawArc( sensorProgressRect.toRectF(), - 0f /* startAngle */, - 360f * progress /* sweepAngle */, - false /* useCenter */, + 0f, /* startAngle */ + 360f * progress, /* sweepAngle */ + false, /* useCenter */ fillPaint, ) } + canvas.restore() + checkMarkDrawable.draw(canvas) } /** Do nothing here, we will control the alpha internally. */ @@ -211,6 +215,7 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att */ fun drawProgressAt(sensorRect: Rect) { this.sensorRect.set(sensorRect) + invalidateSelf() } /** Indicates if accessibility is enabled or not. */ @@ -228,47 +233,21 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att } } - private fun updateProgress(remainingSteps: Int, totalSteps: Int) { + private fun updateProgress(remainingSteps: Int, totalSteps: Int, isRecreating: Boolean) { if (this.remainingSteps == remainingSteps && this.totalSteps == totalSteps) { return } + + // If we are restoring this view from a saved state, set animation duration to 0 to avoid + // animating progress that has already occurred. + if (isRecreating) { + setAnimationTimeToZero() + } else { + restoreAnimationTime() + } + this.remainingSteps = remainingSteps this.totalSteps = totalSteps - if (this.showingHelp) { - if (vibrator != null && isAccessibilityEnabled) { - vibrator.vibrate( - Process.myUid(), - mContext.opPackageName, - VIBRATE_EFFECT_ERROR, - javaClass.getSimpleName() + "::onEnrollmentHelp", - FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES, - ) - } - } else { - // If the first touch is an error, remainingSteps will be -1 and the callback - // doesn't come from onEnrollmentHelp. If we are in the accessibility flow, - // we still would like to vibrate. - if (vibrator != null) { - if (remainingSteps == -1 && isAccessibilityEnabled) { - vibrator.vibrate( - Process.myUid(), - mContext.opPackageName, - VIBRATE_EFFECT_ERROR, - javaClass.getSimpleName() + "::onFirstTouchError", - FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES, - ) - } else if (remainingSteps != -1 && !isAccessibilityEnabled) { - vibrator.vibrate( - Process.myUid(), - mContext.opPackageName, - SUCCESS_VIBRATION_EFFECT, - javaClass.getSimpleName() + "::OnEnrollmentProgress", - HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES, - ) - } - } - } - this.showingHelp = showingHelp this.remainingSteps = remainingSteps this.totalSteps = totalSteps val targetProgress = (totalSteps - remainingSteps).toFloat().div(max(1, totalSteps)) @@ -276,12 +255,69 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att if (progressAnimator != null && progressAnimator!!.isRunning) { progressAnimator!!.cancel() } + /** The [progressUpdateListener] will force re-[draw]s to occur depending on the progress. */ progressAnimator = ValueAnimator.ofFloat(progress, targetProgress).also { - it.setDuration(PROGRESS_ANIMATION_DURATION_MS) + it.setDuration(animateArcDuration) it.addUpdateListener(progressUpdateListener) it.start() } + if (remainingSteps == 0) { + runCompletionAnimation() + } + } + + private fun runCompletionAnimation() { + checkMarkAnimator?.cancel() + + checkMarkAnimator = ValueAnimator.ofFloat(0f, 1f) + checkMarkAnimator?.apply { + startDelay = checkmarkAnimationDelayDuration + setDuration(checkmarkAnimationDuration) + interpolator = OvershootInterpolator() + addUpdateListener { + val newBounds = getCheckMarkStartBounds() + val scale = it.animatedFraction + newBounds.set( + newBounds.left, + newBounds.top, + (newBounds.left + (newBounds.width() * scale)).toInt(), + (newBounds.top + (newBounds.height() * scale)).toInt(), + ) + checkMarkDrawable.bounds = newBounds + checkMarkDrawable.setVisible(true, false) + } + start() + } + } + + /** + * This returns the bounds for which the checkmark drawable should be drawn at. It should be drawn + * on the arc of the progress bar at the 315 degree mark. + */ + private fun getCheckMarkStartBounds(): Rect { + val progressBounds = getSensorProgressRect() + val radius = progressBounds.width() / 2.0 + + var x = (cos(Math.toRadians(315.0)) * radius).toInt() + progressBounds.centerX() + // Remember to negate this value as sin(>180) will return negative value + var y = (-sin(Math.toRadians(315.0)) * radius).toInt() + progressBounds.centerY() + // Subtract height|width /2 to make sure we draw in the middle of the arc. + x -= (checkMarkDrawable.intrinsicWidth / 2.0).toInt() + y -= (checkMarkDrawable.intrinsicHeight / 2.0).toInt() + + return Rect(x, y, x + checkMarkDrawable.intrinsicWidth, y + checkMarkDrawable.intrinsicHeight) + } + + private fun getSensorProgressRect(): Rect { + val sensorProgressRect = Rect(sensorRect) + sensorProgressRect.inset( + -progressBarRadius, + -progressBarRadius, + -progressBarRadius, + -progressBarRadius, + ) + return sensorProgressRect } /** @@ -294,7 +330,7 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att } backgroundColorAnimator = ValueAnimator.ofArgb(backgroundPaint.color, onFirstBucketFailedColor).also { - it.setDuration(FILL_COLOR_ANIMATION_DURATION_MS) + it.setDuration(fillColorAnimationDuration) it.repeatCount = 1 it.repeatMode = ValueAnimator.REVERSE it.interpolator = DEACCEL @@ -315,7 +351,7 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att @ColorInt val targetColor = helpColor fillColorAnimator = ValueAnimator.ofArgb(fillPaint.color, targetColor).also { - it.setDuration(FILL_COLOR_ANIMATION_DURATION_MS) + it.setDuration(fillColorAnimationDuration) it.repeatCount = 1 it.repeatMode = ValueAnimator.REVERSE it.interpolator = DEACCEL @@ -325,33 +361,32 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att } } - private fun startCompletionAnimation() { - if (complete) { - return - } - complete = true + /** + * This sets animation time to 0. This typically happens after an activity recreation, we don't + * want to re-animate the progress/success animation with the default timer + */ + private fun setAnimationTimeToZero() { + fillColorAnimationDuration = 0 + animateArcDuration = 0 + checkmarkAnimationDelayDuration = 0 + checkmarkAnimationDuration = 0 } - private fun rollBackCompletionAnimation() { - if (!complete) { - return - } - complete = false + /** This sets animation timers back to normal, this happens after we have */ + private fun restoreAnimationTime() { + fillColorAnimationDuration = FILL_COLOR_ANIMATION_DURATION_MS + animateArcDuration = PROGRESS_ANIMATION_DURATION_MS + checkmarkAnimationDelayDuration = CHECKMARK_ANIMATION_DELAY_MS + checkmarkAnimationDuration = CHECKMARK_ANIMATION_DURATION_MS } - private fun loadResources(context: Context, attrs: AttributeSet?) {} - companion object { private const val TAG = "UdfpsProgressBar" private const val FILL_COLOR_ANIMATION_DURATION_MS = 350L private const val PROGRESS_ANIMATION_DURATION_MS = 400L + private const val CHECKMARK_ANIMATION_DELAY_MS = 200L + private const val CHECKMARK_ANIMATION_DURATION_MS = 300L private const val STROKE_WIDTH_DP = 12f private val DEACCEL: Interpolator = DecelerateInterpolator() - private val VIBRATE_EFFECT_ERROR = VibrationEffect.createWaveform(longArrayOf(0, 5, 55, 60), -1) - private val FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES = - VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ACCESSIBILITY) - private val HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = - VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK) - private val SUCCESS_VIBRATION_EFFECT = VibrationEffect.get(VibrationEffect.EFFECT_CLICK) } } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollViewV2.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollViewV2.kt index 5af3f4bf5a9..b355f7735d7 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollViewV2.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollViewV2.kt @@ -17,59 +17,99 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.widget import android.content.Context +import android.graphics.Point import android.graphics.Rect import android.util.AttributeSet import android.util.Log +import android.view.DisplayInfo +import android.view.MotionEvent +import android.view.Surface +import android.view.View +import android.view.View.OnHoverListener import android.view.ViewGroup import android.widget.FrameLayout import android.widget.ImageView import com.android.settings.R -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.Acquired -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.OverlayShown -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.PointerDown -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.PointerUp -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.StageViewModel -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsEnrollEvent -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsError -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsHelp -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsProgress +import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState +import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel +import com.android.systemui.biometrics.UdfpsUtils +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams +import com.android.systemui.biometrics.shared.model.toInt /** * View corresponding with fingerprint_v2_udfps_enroll_view.xml. This view is responsible for * drawing the [UdfpsEnrollIconV2] and the [UdfpsEnrollProgressBarDrawableV2]. */ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) { + private lateinit var fingerprintSensorType: FingerprintSensorType + private var onHoverListener: OnHoverListener = OnHoverListener { _, _ -> false } private var isAccessibilityEnabled: Boolean = false private lateinit var sensorRect: Rect private val fingerprintIcon: UdfpsEnrollIconV2 = UdfpsEnrollIconV2(mContext, attrs) private val fingerprintProgressDrawable: UdfpsEnrollProgressBarDrawableV2 = UdfpsEnrollProgressBarDrawableV2(mContext, attrs) - private var mTotalSteps = -1 - private var mRemainingSteps = -1 + private var remainingSteps = -1 + private val udfpsUtils: UdfpsUtils = UdfpsUtils() + private lateinit var touchExplorationAnnouncer: TouchExplorationAnnouncer + private var isRecreating = false /** * This function computes the center (x,y) location with respect to the parent [FrameLayout] for * the [UdfpsEnrollProgressBarDrawableV2]. It also computes the [Rect] with respect to the parent - * [FrameLayout] for the [UdfpsEnrollIconV2]. + * [FrameLayout] for the [UdfpsEnrollIconV2]. This function will also setup the + * [touchExplorationAnnouncer] */ - fun setSensorRect(rect: Rect) { + fun setSensorRect(rect: Rect, sensorType: FingerprintSensorType) { this.sensorRect = rect - + this.fingerprintSensorType = sensorType findViewById(R.id.udfps_enroll_animation_fp_progress_view)?.also { it.setImageDrawable(fingerprintProgressDrawable) } findViewById(R.id.udfps_enroll_animation_fp_view)?.also { it.setImageDrawable(fingerprintIcon) } + + val rotation = display.rotation + var displayInfo = DisplayInfo() + context.display.getDisplayInfo(displayInfo) + val scaleFactor = udfpsUtils.getScaleFactor(displayInfo) + val overlayParams = + UdfpsOverlayParams( + sensorRect, + fingerprintProgressDrawable.bounds, + displayInfo.naturalWidth, + displayInfo.naturalHeight, + scaleFactor, + rotation, + sensorType.toInt(), + ) val parentView = parent as ViewGroup val coords = parentView.getLocationOnScreen() val parentLeft = coords[0] val parentTop = coords[1] val sensorRectOffset = Rect(sensorRect) + // If the view has been rotated, we need to translate the sensor coordinates + // to the new rotated view. + when (rotation) { + Surface.ROTATION_90, + Surface.ROTATION_270 -> { + sensorRectOffset.set( + sensorRectOffset.top, + sensorRectOffset.left, + sensorRectOffset.bottom, + sensorRectOffset.right, + ) + } + else -> {} + } + // Translate the sensor position into UdfpsEnrollView's view space. sensorRectOffset.offset(-parentLeft, -parentTop) fingerprintIcon.drawSensorRectAt(sensorRectOffset) fingerprintProgressDrawable.drawProgressAt(sensorRectOffset) + + touchExplorationAnnouncer = TouchExplorationAnnouncer(context, this, overlayParams, udfpsUtils) } /** Updates the current enrollment stage. */ @@ -78,15 +118,17 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co } /** Receive enroll progress event */ - fun onUdfpsEvent(event: UdfpsEnrollEvent) { + fun onUdfpsEvent(event: FingerEnrollState) { when (event) { - is UdfpsProgress -> onEnrollmentProgress(event.remainingSteps, event.totalSteps) - is Acquired -> onAcquired(event.acquiredGood) - is UdfpsHelp -> onEnrollmentHelp() - is PointerDown -> onPointerDown() - is PointerUp -> onPointerUp() - OverlayShown -> overlayShown() - is UdfpsError -> udfpsError(event.errMsgId, event.errString) + is FingerEnrollState.EnrollProgress -> + onEnrollmentProgress(event.remainingSteps, event.totalStepsRequired) + is FingerEnrollState.Acquired -> onAcquired(event.acquiredGood) + is FingerEnrollState.EnrollHelp -> onEnrollmentHelp() + is FingerEnrollState.PointerDown -> onPointerDown() + is FingerEnrollState.PointerUp -> onPointerUp() + is FingerEnrollState.OverlayShown -> overlayShown() + is FingerEnrollState.EnrollError -> + throw IllegalArgumentException("$TAG should not handle udfps error") } } @@ -94,9 +136,37 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co fun setAccessibilityEnabled(enabled: Boolean) { this.isAccessibilityEnabled = enabled fingerprintProgressDrawable.setAccessibilityEnabled(enabled) + if (enabled) { + addHoverListener() + } else { + clearHoverListener() + } } private fun udfpsError(errMsgId: Int, errString: String) {} + /** + * Sends a touch exploration event to the [onHoverListener] this should only be used for + * debugging. + */ + fun sendDebugTouchExplorationEvent(motionEvent: MotionEvent) { + touchExplorationAnnouncer.onTouch(motionEvent) + } + + /** Sets the addHoverListener, this should happen when talkback is enabled. */ + private fun addHoverListener() { + onHoverListener = OnHoverListener { _: View, event: MotionEvent -> + sendDebugTouchExplorationEvent(event) + false + } + this.setOnHoverListener(onHoverListener) + } + + /** Clears the hover listener if one was set. */ + private fun clearHoverListener() { + val listener = OnHoverListener { _, _ -> false } + this.setOnHoverListener(listener) + onHoverListener = listener + } private fun overlayShown() { Log.e(TAG, "Implement overlayShown") @@ -115,7 +185,7 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co /** Receive onAcquired event */ private fun onAcquired(isAcquiredGood: Boolean) { - val animateIfLastStepGood = isAcquiredGood && mRemainingSteps <= 2 && mRemainingSteps >= 0 + val animateIfLastStepGood = isAcquiredGood && remainingSteps <= 2 && remainingSteps >= 0 if (animateIfLastStepGood) fingerprintProgressDrawable.onLastStepAcquired() } @@ -129,6 +199,52 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co fingerprintIcon.startDrawing() } + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + super.onLayout(changed, left, top, right, bottom) + // Because the layout has changed, we need to recompute all locations. + if (this::sensorRect.isInitialized && this::fingerprintSensorType.isInitialized) { + setSensorRect(sensorRect, fingerprintSensorType) + } + } + + /** + * This class is responsible for announcing touch events that are outside of the sensort rect + * area. Generally, if a touch is to the left of the sensor, the accessibility announcement will + * be something like "move right" + */ + private class TouchExplorationAnnouncer( + val context: Context, + val view: View, + val overlayParams: UdfpsOverlayParams, + val udfpsUtils: UdfpsUtils, + ) { + /** Will announce accessibility event for touches outside of the sensor rect. */ + fun onTouch(event: MotionEvent) { + val scaledTouch: Point = + udfpsUtils.getTouchInNativeCoordinates(event.getPointerId(0), event, overlayParams) + if (udfpsUtils.isWithinSensorArea(event.getPointerId(0), event, overlayParams)) { + return + } + val theStr: String = + udfpsUtils.onTouchOutsideOfSensorArea( + true /*touchExplorationEnabled*/, + context, + scaledTouch.x, + scaledTouch.y, + overlayParams, + ) + if (theStr != null) { + view.announceForAccessibility(theStr) + } + } + } + + /** Indicates we should should restore the views saved state. */ + fun onEnrollProgressSaved(it: FingerEnrollState.EnrollProgress) { + fingerprintIcon.onEnrollmentProgress(it.remainingSteps, it.totalStepsRequired, true) + fingerprintProgressDrawable.onEnrollmentProgress(it.remainingSteps, it.totalStepsRequired, true) + } + companion object { private const val TAG = "UdfpsEnrollView" } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt index 63182bb6bb9..7ddb142c01e 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt @@ -18,9 +18,11 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import com.android.systemui.biometrics.shared.model.FingerprintSensor import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.flow.update @@ -63,7 +65,7 @@ class FingerprintEnrollEnrollingViewModel( } /** Collects the enrollment flow based on [enrollFlowShouldBeRunning] */ - val enrollFLow = + val enrollFlow = enrollFlowShouldBeRunning.transformLatest { if (it) { fingerprintEnrollViewModel.enrollFlow.collect { event -> emit(event) } 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 3bf806b7eaf..ddbf1cbd777 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 @@ -25,7 +25,6 @@ import com.android.settings.biometrics.fingerprint2.domain.interactor.Orientatio import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor 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.fragment.FingerprintEnrollFindSensorV2Fragment import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Education import com.android.systemui.biometrics.shared.model.FingerprintSensorType import kotlinx.coroutines.flow.Flow @@ -38,7 +37,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -/** Models the UI state for [FingerprintEnrollFindSensorV2Fragment]. */ +/** Models the UI state for fingerprint enroll education */ class FingerprintEnrollFindSensorViewModel( private val navigationViewModel: FingerprintNavigationViewModel, private val fingerprintEnrollViewModel: FingerprintEnrollViewModel, @@ -70,7 +69,7 @@ class FingerprintEnrollFindSensorViewModel( combineTransform( _showSfpsLottie, foldStateInteractor.isFolded, - orientationInteractor.rotation, + orientationInteractor.rotationFromDefault, ) { _, isFolded, rotation -> emit(Pair(isFolded, rotation)) } @@ -147,6 +146,7 @@ class FingerprintEnrollFindSensorViewModel( } } is FingerEnrollState.EnrollHelp -> {} + else -> {} } } } 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 bd905242fc3..7900ed725bc 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 @@ -45,12 +45,14 @@ 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.domain.interactor.FingerprintEnrollInteractorImpl import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepositoryImpl -import com.android.settings.biometrics.fingerprint2.domain.interactor.PressToAuthInteractorImpl 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 import com.android.settings.biometrics.fingerprint2.lib.model.Settings +import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.util.toFingerprintEnrollOptions import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsViewModel @@ -222,6 +224,13 @@ class FingerprintSettingsV2Fragment : val fingerprintSensorProvider = FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, lifecycleScope) val pressToAuthInteractor = PressToAuthInteractorImpl(context, backgroundDispatcher) + val fingerprintEnrollStateRepository = + FingerprintEnrollInteractorImpl( + requireContext().applicationContext, + intent.toFingerprintEnrollOptions(), + fingerprintManager, + Settings, + ) val interactor = FingerprintManagerInteractorImpl( @@ -230,9 +239,7 @@ class FingerprintSettingsV2Fragment : fingerprintManager, fingerprintSensorProvider, GatekeeperPasswordProvider(LockPatternUtils(context.applicationContext)), - pressToAuthInteractor, - Settings, - getIntent() + fingerprintEnrollStateRepository, ) val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN) 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 4d2865040cb..ebdc504c1a8 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 @@ -91,6 +91,7 @@ class Injector(step: FingerprintNavigationStep.UiStep) { object : OrientationInteractor { override val orientation: Flow = flowOf(Configuration.ORIENTATION_LANDSCAPE) override val rotation: Flow = flowOf(Surface.ROTATION_0) + override val rotationFromDefault: Flow = rotation override fun getRotationFromDefault(rotation: Int): Int = rotation } diff --git a/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/fragment/FingerprintEnrollFindSensorScreenshotTest.kt b/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/fragment/RfpsEnrollFindSensorScreenshotTest.kt similarity index 73% rename from tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/fragment/FingerprintEnrollFindSensorScreenshotTest.kt rename to tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/fragment/RfpsEnrollFindSensorScreenshotTest.kt index 18257c2121b..594aade20bb 100644 --- a/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/fragment/FingerprintEnrollFindSensorScreenshotTest.kt +++ b/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/fragment/RfpsEnrollFindSensorScreenshotTest.kt @@ -17,9 +17,7 @@ package com.android.settings.tests.screenshot.biometrics.fingerprint.fragment */ import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollFindSensorV2Fragment -import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep -import com.android.settings.tests.screenshot.biometrics.fingerprint.Injector +import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education.RfpsEnrollFindSensorFragment import com.android.settings.tests.screenshot.biometrics.fingerprint.Injector.Companion.BiometricFragmentScreenShotRule import org.junit.Rule import org.junit.Test @@ -28,10 +26,7 @@ import platform.test.screenshot.FragmentScreenshotTestRule import platform.test.screenshot.ViewScreenshotTestRule.Mode @RunWith(AndroidJUnit4::class) -class FingerprintEnrollFindSensorScreenshotTest { - private val injector: Injector = - Injector(FingerprintNavigationStep.Education(Injector.interactor.sensorProp)) - +class RfpsEnrollFindSensorScreenshotTest { @Rule @JvmField var rule: FragmentScreenshotTestRule = BiometricFragmentScreenShotRule() @Test @@ -39,7 +34,7 @@ class FingerprintEnrollFindSensorScreenshotTest { rule.screenshotTest( "fp_enroll_find_sensor", Mode.MatchSize, - FingerprintEnrollFindSensorV2Fragment(injector.fingerprintSensor.sensorType, injector.factory), + RfpsEnrollFindSensorFragment(), ) } } 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 58d18f69db0..900afd1d55d 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 @@ -33,8 +33,8 @@ import android.os.Handler import androidx.test.core.app.ApplicationProvider import com.android.settings.biometrics.GatekeeperPasswordProvider 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.domain.interactor.PressToAuthInteractor import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor import com.android.settings.biometrics.fingerprint2.lib.model.Default import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason @@ -82,10 +82,6 @@ class FingerprintManagerInteractorTest { @Mock private lateinit var gateKeeperPasswordProvider: GatekeeperPasswordProvider private var testScope = TestScope(backgroundDispatcher) - private var pressToAuthInteractor = - object : PressToAuthInteractor { - override val isEnabled = flowOf(false) - } @Before fun setup() { @@ -113,9 +109,12 @@ class FingerprintManagerInteractorTest { fingerprintManager, fingerprintSensorRepository, gateKeeperPasswordProvider, - pressToAuthInteractor, - Default, - Intent(), + FingerprintEnrollInteractorImpl( + context, + FingerprintEnrollOptions.Builder().build(), + fingerprintManager, + Default, + ), ) } 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 5fcd772cdcc..4906e845f87 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 @@ -145,7 +145,8 @@ class FingerprintEnrollFindSensorViewModelV2Test { orientationInteractor = object : OrientationInteractor { override val orientation: Flow = flowOf(Configuration.ORIENTATION_LANDSCAPE) - override val rotation: Flow = flowOf(Surface.ROTATION_0) + override val rotation: Flow = flowOf(Surface.ROTATION_0) + override val rotationFromDefault: Flow = flowOf(Surface.ROTATION_0) override fun getRotationFromDefault(rotation: Int): Int = rotation } underTest = From 9447b63197b3618dbafe244b103db4c9fba18e9c Mon Sep 17 00:00:00 2001 From: Manish Singh Date: Fri, 17 May 2024 11:43:27 +0000 Subject: [PATCH 03/17] Kill tasks that are neither visible nor in focus Bug: 336754145 Test: manual Change-Id: I559886b172c0f2e6a5d9882f8109b8d51644315c --- .../android/settings/privatespace/PrivateSpaceMaintainer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java index 38f724a4d7b..db7fb4357e3 100644 --- a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java +++ b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java @@ -448,7 +448,7 @@ public class PrivateSpaceMaintainer { private void removeSettingsAllTasks() { List appTasks = mActivityManager.getAppTasks(); for (var appTask : appTasks) { - if (!appTask.getTaskInfo().isVisible()) { + if (!(appTask.getTaskInfo().isVisible() || appTask.getTaskInfo().isFocused)) { appTask.finishAndRemoveTask(); } } From b42553dccc1ab4e6a4839ccb2fe044523870a0e3 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Thu, 16 May 2024 16:26:33 -0400 Subject: [PATCH 04/17] Some minor additions (icons, sorting) to modes Test: manually viewing pages Flag: android.app.modes_ui Fixes: 331429435 Fixes: 308819461 Bug: 322373473 Change-Id: I17e6df82e17a4cfadc2948258829746cdb5473db --- res/drawable/ic_modes_event.xml | 25 +++++++ res/drawable/ic_modes_time.xml | 26 +++++++ res/xml/modes_rule_settings.xml | 6 +- .../settings/notification/modes/ZenMode.java | 4 +- .../notification/modes/ZenModeFragment.java | 14 +--- .../modes/ZenModeHeaderController.java | 75 +++++++++++++++++++ .../modes/ZenModeListPreference.java | 20 ++++- .../notification/modes/ZenModesBackend.java | 13 +++- .../ZenModesListPreferenceController.java | 34 +++++++-- 9 files changed, 185 insertions(+), 32 deletions(-) create mode 100644 res/drawable/ic_modes_event.xml create mode 100644 res/drawable/ic_modes_time.xml create mode 100644 src/com/android/settings/notification/modes/ZenModeHeaderController.java diff --git a/res/drawable/ic_modes_event.xml b/res/drawable/ic_modes_event.xml new file mode 100644 index 00000000000..40004899ad8 --- /dev/null +++ b/res/drawable/ic_modes_event.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/res/drawable/ic_modes_time.xml b/res/drawable/ic_modes_time.xml new file mode 100644 index 00000000000..dff3c43933d --- /dev/null +++ b/res/drawable/ic_modes_time.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/res/xml/modes_rule_settings.xml b/res/xml/modes_rule_settings.xml index d7e26946829..1b79153cf7b 100644 --- a/res/xml/modes_rule_settings.xml +++ b/res/xml/modes_rule_settings.xml @@ -18,8 +18,8 @@ - - + \ No newline at end of file diff --git a/src/com/android/settings/notification/modes/ZenMode.java b/src/com/android/settings/notification/modes/ZenMode.java index 66d68c5c7cd..51c92e6eae5 100644 --- a/src/com/android/settings/notification/modes/ZenMode.java +++ b/src/com/android/settings/notification/modes/ZenMode.java @@ -155,8 +155,8 @@ class ZenMode { int iconResIdFromType = switch (mRule.getType()) { case AutomaticZenRule.TYPE_UNKNOWN -> R.drawable.ic_do_not_disturb_on_24dp; case AutomaticZenRule.TYPE_OTHER -> R.drawable.ic_do_not_disturb_on_24dp; - case AutomaticZenRule.TYPE_SCHEDULE_TIME -> R.drawable.ic_do_not_disturb_on_24dp; - case AutomaticZenRule.TYPE_SCHEDULE_CALENDAR -> R.drawable.ic_do_not_disturb_on_24dp; + case AutomaticZenRule.TYPE_SCHEDULE_TIME -> R.drawable.ic_modes_time; + case AutomaticZenRule.TYPE_SCHEDULE_CALENDAR -> R.drawable.ic_modes_event; case AutomaticZenRule.TYPE_BEDTIME -> R.drawable.ic_do_not_disturb_on_24dp; case AutomaticZenRule.TYPE_DRIVING -> R.drawable.ic_do_not_disturb_on_24dp; case AutomaticZenRule.TYPE_IMMERSIVE -> R.drawable.ic_do_not_disturb_on_24dp; diff --git a/src/com/android/settings/notification/modes/ZenModeFragment.java b/src/com/android/settings/notification/modes/ZenModeFragment.java index 616332e0d1f..d2126810358 100644 --- a/src/com/android/settings/notification/modes/ZenModeFragment.java +++ b/src/com/android/settings/notification/modes/ZenModeFragment.java @@ -41,6 +41,7 @@ public class ZenModeFragment extends ZenModeFragmentBase { // TODO: fill in with all the elements of this page. Each should be an instance of // {@link AbstractZenModePreferenceController}. List prefControllers = new ArrayList<>(); + prefControllers.add(new ZenModeHeaderController(context, "header", this, mBackend)); return prefControllers; } @@ -55,19 +56,6 @@ public class ZenModeFragment extends ZenModeFragmentBase { return; } getActivity().setTitle(azr.getName()); - - // TODO: b/308819292 - implement the real screen! - final PreferenceScreen screen = getPreferenceScreen(); - if (screen == null) { - return; - } - - Preference tmpPref = screen.findPreference("zen_mode_test"); - if (tmpPref == null) { - return; - } - tmpPref.setTitle(azr.getTriggerDescription()); - tmpPref.setSummary("active?: " + mode.isActive()); } @Override diff --git a/src/com/android/settings/notification/modes/ZenModeHeaderController.java b/src/com/android/settings/notification/modes/ZenModeHeaderController.java new file mode 100644 index 00000000000..246eee85d77 --- /dev/null +++ b/src/com/android/settings/notification/modes/ZenModeHeaderController.java @@ -0,0 +1,75 @@ +/* + * 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 android.app.Flags; +import android.content.Context; +import android.graphics.drawable.Drawable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.preference.Preference; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.widget.EntityHeaderController; +import com.android.settingslib.widget.LayoutPreference; + +import java.util.concurrent.TimeUnit; + +public class ZenModeHeaderController extends AbstractZenModePreferenceController { + + private final DashboardFragment mFragment; + private EntityHeaderController mHeaderController; + + ZenModeHeaderController( + @NonNull Context context, + @NonNull String key, + @NonNull DashboardFragment fragment, + @Nullable ZenModesBackend backend) { + super(context, key, backend); + mFragment = fragment; + } + + @Override + public boolean isAvailable() { + return Flags.modesApi(); + } + + @Override + public void updateState(Preference preference) { + if (getAZR() == null || mFragment == null) { + return; + } + + if (mHeaderController == null) { + final LayoutPreference pref = (LayoutPreference) preference; + mHeaderController = EntityHeaderController.newInstance( + mFragment.getActivity(), + mFragment, + pref.findViewById(R.id.entity_header)); + } + Drawable icon = null; + try { + icon = getMode().getIcon(mContext).get(200, TimeUnit.MILLISECONDS); + } catch (Exception e) { + // no icon + } + mHeaderController.setIcon(icon) + .setLabel(getAZR().getName()) + .done(false /* rebindActions */); + } +} diff --git a/src/com/android/settings/notification/modes/ZenModeListPreference.java b/src/com/android/settings/notification/modes/ZenModeListPreference.java index cb0456140bf..2a95dffc5e0 100644 --- a/src/com/android/settings/notification/modes/ZenModeListPreference.java +++ b/src/com/android/settings/notification/modes/ZenModeListPreference.java @@ -25,6 +25,10 @@ import com.android.settings.core.SubSettingLauncher; import com.android.settings.notification.zen.ZenModeSettings; import com.android.settingslib.RestrictedPreference; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + /** * Preference representing a single mode item on the modes aggregator page. Clicking on this * preference leads to an individual mode's configuration page. @@ -36,10 +40,8 @@ public class ZenModeListPreference extends RestrictedPreference { ZenModeListPreference(Context context, ZenMode zenMode) { super(context); mContext = context; - mZenMode = zenMode; - setTitle(mZenMode.getRule().getName()); - setSummary((mZenMode.isActive() ? "ACTIVE" : "inactive") + ": " - + mZenMode.getRule().getTriggerDescription()); + setZenMode(zenMode); + setKey(zenMode.getId()); } @Override @@ -60,6 +62,16 @@ public class ZenModeListPreference extends RestrictedPreference { .setSourceMetricsCategory(SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION) .launch(); } + } + public void setZenMode(ZenMode zenMode) { + mZenMode = zenMode; + setTitle(mZenMode.getRule().getName()); + setSummary(mZenMode.getRule().getTriggerDescription()); + try { + setIcon(mZenMode.getIcon(mContext).get(200, TimeUnit.MILLISECONDS)); + } catch (Exception e) { + // no icon + } } } diff --git a/src/com/android/settings/notification/modes/ZenModesBackend.java b/src/com/android/settings/notification/modes/ZenModesBackend.java index 373d6540acc..e019ea27940 100644 --- a/src/com/android/settings/notification/modes/ZenModesBackend.java +++ b/src/com/android/settings/notification/modes/ZenModesBackend.java @@ -33,6 +33,7 @@ import com.android.settings.R; import java.time.Duration; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.Map; @@ -77,7 +78,15 @@ class ZenModesBackend { isRuleActive(ruleId, currentConfig))); } - // TODO: b/331429435 - Sort modes. + modes.sort((l, r) -> { + if (l.isManualDnd()) { + return -1; + } else if (r.isManualDnd()) { + return 1; + } + return l.getRule().getName().compareTo(r.getRule().getName()); + }); + return modes; } @@ -105,10 +114,10 @@ class ZenModesBackend { .setZenPolicy(ZenAdapters.notificationPolicyToZenPolicy( mNotificationManager.getNotificationPolicy())) .setDeviceEffects(null) - .setTriggerDescription(mContext.getString(R.string.zen_mode_settings_summary)) .setManualInvocationAllowed(true) .setConfigurationActivity(null) // No further settings .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY) + .setIconResId(com.android.internal.R.drawable.ic_zen_24dp) .build(); return ZenMode.manualDndMode(manualDndRule, diff --git a/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java b/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java index 53336c84be3..dbeaa2cceca 100644 --- a/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java @@ -15,6 +15,7 @@ */ package com.android.settings.notification.modes; +import android.app.AutomaticZenRule; import android.app.Flags; import android.content.Context; @@ -26,6 +27,9 @@ import androidx.preference.PreferenceCategory; import com.android.settingslib.core.AbstractPreferenceController; +import java.util.HashMap; +import java.util.Map; + /** * Controller for the PreferenceCategory on the modes aggregator page ({@link ZenModesListFragment}) * containing links to each individual mode. This is a central controller that populates and updates @@ -65,16 +69,30 @@ public class ZenModesListPreferenceController extends AbstractPreferenceControll // category for each rule that exists. PreferenceCategory category = (PreferenceCategory) preference; - // TODO: b/322373473 - This is not the right way to replace these preferences; we should - // follow something similar to what - // ZenModeAutomaticRulesPreferenceController does to change rules - // only as necessary and update them. - category.removeAll(); + Map originalPreferences = new HashMap<>(); + for (int i = 0; i < category.getPreferenceCount(); i++) { + ZenModeListPreference pref = (ZenModeListPreference) category.getPreference(i); + originalPreferences.put(pref.getKey(), pref); + } + // Loop through each rule, either updating the existing rule or creating the rule's + // preference for (ZenMode mode : mBackend.getModes()) { - Preference pref = new ZenModeListPreference(mContext, mode); - category.addPreference(pref); + if (originalPreferences.containsKey(mode.getId())) { + // existing rule; update its info if it's changed since the last display + AutomaticZenRule rule = mode.getRule(); + originalPreferences.get(mode.getId()).setZenMode(mode); + } else { + // new rule; create a new ZenRulePreference & add it to the preference category + Preference pref = new ZenModeListPreference(mContext, mode); + category.addPreference(pref); + } + + originalPreferences.remove(mode.getId()); + } + // Remove preferences that no longer have a rule + for (String key : originalPreferences.keySet()) { + category.removePreferenceRecursively(key); } } - } From 35d91e98706f562ae9bda15121a1dc665488e1ef Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Fri, 17 May 2024 15:05:01 +0800 Subject: [PATCH 05/17] Fix EnabledNetworkModePreferenceControllerTest Fix: 337418033 Test: atest EnabledNetworkModePreferenceControllerTest Change-Id: I5614a6d863b804442cbb14c1fbc136db9d800a28 --- tests/unit/Android.bp | 6 ++---- .../EnabledNetworkModePreferenceControllerTest.java | 9 ++++----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp index c8e886cad10..bc5824f6cb2 100644 --- a/tests/unit/Android.bp +++ b/tests/unit/Android.bp @@ -21,18 +21,16 @@ android_test { static_libs: [ "aconfig_settings_flags_lib", "androidx.arch.core_core-testing", - "androidx.test.core", + "androidx.lifecycle_lifecycle-runtime-testing", "androidx.test.espresso.core", - "androidx.test.rules", "androidx.test.ext.junit", - "androidx.preference_preference", + "androidx.test.rules", "flag-junit", "mockito-target-minus-junit4", "platform-test-annotations", "platform-test-rules", "truth", "kotlinx_coroutines_test", - "flag-junit", "Settings-testutils2", "MediaDrmSettingsFlagsLib", // Don't add SettingsLib libraries here - you can use them directly as they are in the diff --git a/tests/unit/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceControllerTest.java index 85c5c6b8f6c..b3d095e5453 100644 --- a/tests/unit/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceControllerTest.java @@ -48,6 +48,7 @@ import android.telephony.TelephonyManager; import androidx.fragment.app.FragmentManager; import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.testing.TestLifecycleOwner; import androidx.preference.ListPreference; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; @@ -60,7 +61,6 @@ import com.android.settings.network.telephony.TelephonyConstants.TelephonyManage import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -113,6 +113,7 @@ public class EnabledNetworkModePreferenceControllerTest { doReturn(mPersistableBundle).when(mCarrierConfigCache).getConfig(); doReturn(mPersistableBundle).when(mCarrierConfigCache).getConfigForSubId(SUB_ID); mPersistableBundle.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true); + mPersistableBundle.putBoolean(CarrierConfigManager.KEY_PREFER_3G_VISIBILITY_BOOL, true); mPreference = new ListPreference(mContext); mController = new EnabledNetworkModePreferenceController(mContext, KEY); mockAllowedNetworkTypes(ALLOWED_ALL_NETWORK_TYPE); @@ -347,7 +348,6 @@ public class EnabledNetworkModePreferenceControllerTest { @UiThreadTest @Test - @Ignore("b/337418033") public void updateState_updateByNetworkMode() { mockEnabledNetworkMode(TelephonyManagerConstants.NETWORK_MODE_TDSCDMA_GSM_WCDMA); @@ -379,7 +379,6 @@ public class EnabledNetworkModePreferenceControllerTest { @UiThreadTest @Test - @Ignore("b/337418033") public void onPreferenceChange_updateSuccess() { mockEnabledNetworkMode(TelephonyManagerConstants.NETWORK_MODE_LTE_GSM_WCDMA); doReturn(true).when(mTelephonyManager).setPreferredNetworkTypeBitmask( @@ -387,6 +386,7 @@ public class EnabledNetworkModePreferenceControllerTest { TelephonyManagerConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA)); mController.updateState(mPreference); + mController.onViewCreated(new TestLifecycleOwner()); mController.onPreferenceChange(mPreference, String.valueOf(TelephonyManagerConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA)); @@ -396,13 +396,13 @@ public class EnabledNetworkModePreferenceControllerTest { @UiThreadTest @Test - @Ignore("b/337418033") public void onPreferenceChange_updateFail() { mockEnabledNetworkMode(TelephonyManagerConstants.NETWORK_MODE_LTE_GSM_WCDMA); doReturn(false).when(mTelephonyManager).setPreferredNetworkTypeBitmask( getRafFromNetworkType(TelephonyManagerConstants.NETWORK_MODE_LTE_GSM_WCDMA)); mController.updateState(mPreference); + mController.onViewCreated(new TestLifecycleOwner()); mController.onPreferenceChange(mPreference, String.valueOf(TelephonyManagerConstants.NETWORK_MODE_LTE_GSM_WCDMA)); @@ -412,7 +412,6 @@ public class EnabledNetworkModePreferenceControllerTest { @UiThreadTest @Test - @Ignore("b/337418033") public void preferredNetworkModeNotification_preferenceUpdates() { final PreferenceManager preferenceManager = new PreferenceManager(mContext); From fb48926fe84e4ace980d4a95c238f5265e67f422 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Mon, 20 May 2024 10:35:01 +0800 Subject: [PATCH 06/17] Reapply "Migrate AppOps" This reverts commit 19b01bd68b9c989e7323eccabb8fa3116a60300b. Bug: 339846642 Test: manual - on Special app access Change-Id: I000274ae9c725f848e1c8910120ba886fcc5be95 --- .../specialaccess/AlarmsAndRemindersAppList.kt | 9 +++++++-- .../spa/app/specialaccess/AllFilesAccess.kt | 7 +++++-- .../spa/app/specialaccess/DisplayOverOtherApps.kt | 3 ++- .../spa/app/specialaccess/InstallUnknownApps.kt | 11 ++++------- .../app/specialaccess/LongBackgroundTasksApps.kt | 7 +++++-- .../spa/app/specialaccess/MediaManagementApps.kt | 7 +++++-- .../spa/app/specialaccess/MediaRoutingControl.kt | 7 +++++-- .../spa/app/specialaccess/ModifySystemSettings.kt | 3 ++- .../spa/app/specialaccess/PictureInPicture.kt | 11 ++++------- .../spa/app/specialaccess/TurnScreenOnApps.kt | 8 +++++--- .../spa/app/specialaccess/UseFullScreenIntent.kt | 7 +++++-- .../spa/app/specialaccess/WifiControlApps.kt | 7 +++++-- .../spa/app/WifiControlAppListModelTest.kt | 2 +- .../spa/app/specialaccess/AllFilesAccessTest.kt | 9 +++++++-- .../specialaccess/LongBackgroundTasksAppsTest.kt | 9 +++++++-- .../app/specialaccess/MediaManagementAppsTest.kt | 9 +++++++-- .../app/specialaccess/MediaRoutingControlTest.kt | 15 ++++++++++----- .../spa/app/specialaccess/PictureInPictureTest.kt | 3 ++- .../spa/app/specialaccess/TurnScreenOnAppsTest.kt | 9 +++++++-- 19 files changed, 95 insertions(+), 48 deletions(-) diff --git a/src/com/android/settings/spa/app/specialaccess/AlarmsAndRemindersAppList.kt b/src/com/android/settings/spa/app/specialaccess/AlarmsAndRemindersAppList.kt index 2b8d12d12d0..b15675ed14f 100644 --- a/src/com/android/settings/spa/app/specialaccess/AlarmsAndRemindersAppList.kt +++ b/src/com/android/settings/spa/app/specialaccess/AlarmsAndRemindersAppList.kt @@ -28,6 +28,7 @@ import androidx.compose.runtime.Composable import com.android.settings.overlay.FeatureFactory.Companion.featureFactory import com.android.settingslib.R import com.android.settingslib.spa.lifecycle.collectAsCallbackWithLifecycle +import com.android.settingslib.spaprivileged.model.app.AppOps import com.android.settingslib.spaprivileged.model.app.AppOpsController import com.android.settingslib.spaprivileged.model.app.AppRecord import com.android.settingslib.spaprivileged.model.app.IPackageManagers @@ -116,8 +117,7 @@ class AlarmsAndRemindersAppListModel( controller = AppOpsController( context = context, app = app, - op = AppOpsManager.OP_SCHEDULE_EXACT_ALARM, - setModeByUid = true, + appOps = APP_OPS, ), ) } @@ -136,6 +136,11 @@ class AlarmsAndRemindersAppListModel( } companion object { + private val APP_OPS = AppOps( + op = AppOpsManager.OP_SCHEDULE_EXACT_ALARM, + setModeByUid = true, + ) + private const val PERMISSION: String = Manifest.permission.SCHEDULE_EXACT_ALARM /** Checks whether [Manifest.permission.SCHEDULE_EXACT_ALARM] is enabled. */ diff --git a/src/com/android/settings/spa/app/specialaccess/AllFilesAccess.kt b/src/com/android/settings/spa/app/specialaccess/AllFilesAccess.kt index d82045d17b4..f6c08c0ca7c 100644 --- a/src/com/android/settings/spa/app/specialaccess/AllFilesAccess.kt +++ b/src/com/android/settings/spa/app/specialaccess/AllFilesAccess.kt @@ -22,6 +22,7 @@ import android.app.settings.SettingsEnums import android.content.Context import com.android.settings.R import com.android.settings.overlay.FeatureFactory.Companion.featureFactory +import com.android.settingslib.spaprivileged.model.app.AppOps import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider @@ -35,9 +36,11 @@ class AllFilesAccessListModel(context: Context) : AppOpPermissionListModel(conte override val pageTitleResId = R.string.manage_external_storage_title override val switchTitleResId = R.string.permit_manage_external_storage override val footerResId = R.string.allow_manage_external_storage_description - override val appOp = AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE + override val appOps = AppOps( + op = AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE, + setModeByUid = true, + ) override val permission = Manifest.permission.MANAGE_EXTERNAL_STORAGE - override val setModeByUid = true override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) { super.setAllowed(record, newAllowed) diff --git a/src/com/android/settings/spa/app/specialaccess/DisplayOverOtherApps.kt b/src/com/android/settings/spa/app/specialaccess/DisplayOverOtherApps.kt index 26d74518411..904f0afd3d6 100644 --- a/src/com/android/settings/spa/app/specialaccess/DisplayOverOtherApps.kt +++ b/src/com/android/settings/spa/app/specialaccess/DisplayOverOtherApps.kt @@ -22,6 +22,7 @@ import android.app.settings.SettingsEnums import android.content.Context import com.android.settings.R import com.android.settings.overlay.FeatureFactory.Companion.featureFactory +import com.android.settingslib.spaprivileged.model.app.AppOps import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider @@ -35,7 +36,7 @@ class DisplayOverOtherAppsListModel(context: Context) : AppOpPermissionListModel override val pageTitleResId = R.string.system_alert_window_settings override val switchTitleResId = R.string.permit_draw_overlay override val footerResId = R.string.allow_overlay_description - override val appOp = AppOpsManager.OP_SYSTEM_ALERT_WINDOW + override val appOps = AppOps(AppOpsManager.OP_SYSTEM_ALERT_WINDOW) override val permission = Manifest.permission.SYSTEM_ALERT_WINDOW override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) { diff --git a/src/com/android/settings/spa/app/specialaccess/InstallUnknownApps.kt b/src/com/android/settings/spa/app/specialaccess/InstallUnknownApps.kt index 18ce585f1ee..ace7949533f 100644 --- a/src/com/android/settings/spa/app/specialaccess/InstallUnknownApps.kt +++ b/src/com/android/settings/spa/app/specialaccess/InstallUnknownApps.kt @@ -20,7 +20,6 @@ import android.Manifest import android.app.AppGlobals import android.app.AppOpsManager import android.app.AppOpsManager.MODE_DEFAULT -import android.app.AppOpsManager.OP_REQUEST_INSTALL_PACKAGES import android.content.Context import android.content.pm.ApplicationInfo import android.os.Process @@ -28,6 +27,7 @@ import android.os.UserManager import androidx.compose.runtime.Composable import com.android.settings.R import com.android.settingslib.spa.lifecycle.collectAsCallbackWithLifecycle +import com.android.settingslib.spaprivileged.model.app.AppOps import com.android.settingslib.spaprivileged.model.app.AppOpsController import com.android.settingslib.spaprivileged.model.app.AppRecord import com.android.settingslib.spaprivileged.model.app.userId @@ -62,12 +62,7 @@ class InstallUnknownAppsListModel(private val context: Context) : override fun transformItem(app: ApplicationInfo) = InstallUnknownAppsRecord( app = app, - appOpsController = - AppOpsController( - context = context, - app = app, - op = OP_REQUEST_INSTALL_PACKAGES, - ), + appOpsController = AppOpsController(context = context, app = app, appOps = APP_OPS), ) override fun filter( @@ -92,6 +87,8 @@ class InstallUnknownAppsListModel(private val context: Context) : } companion object { + private val APP_OPS = AppOps(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES) + private fun isChangeable( record: InstallUnknownAppsRecord, potentialPackageNames: Set, diff --git a/src/com/android/settings/spa/app/specialaccess/LongBackgroundTasksApps.kt b/src/com/android/settings/spa/app/specialaccess/LongBackgroundTasksApps.kt index 3ba9b085d23..d897b5231db 100644 --- a/src/com/android/settings/spa/app/specialaccess/LongBackgroundTasksApps.kt +++ b/src/com/android/settings/spa/app/specialaccess/LongBackgroundTasksApps.kt @@ -22,6 +22,7 @@ import android.app.settings.SettingsEnums import android.content.Context import com.android.settings.R import com.android.settings.overlay.FeatureFactory.Companion.featureFactory +import com.android.settingslib.spaprivileged.model.app.AppOps import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider @@ -35,9 +36,11 @@ class LongBackgroundTasksAppsListModel(context: Context) : AppOpPermissionListMo override val pageTitleResId = R.string.long_background_tasks_title override val switchTitleResId = R.string.long_background_tasks_switch_title override val footerResId = R.string.long_background_tasks_footer_title - override val appOp = AppOpsManager.OP_RUN_USER_INITIATED_JOBS + override val appOps = AppOps( + op = AppOpsManager.OP_RUN_USER_INITIATED_JOBS, + setModeByUid = true, + ) override val permission = Manifest.permission.RUN_USER_INITIATED_JOBS - override val setModeByUid = true override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) { super.setAllowed(record, newAllowed) diff --git a/src/com/android/settings/spa/app/specialaccess/MediaManagementApps.kt b/src/com/android/settings/spa/app/specialaccess/MediaManagementApps.kt index 3e3457c2018..ce6abbdf2b8 100644 --- a/src/com/android/settings/spa/app/specialaccess/MediaManagementApps.kt +++ b/src/com/android/settings/spa/app/specialaccess/MediaManagementApps.kt @@ -22,6 +22,7 @@ import android.app.settings.SettingsEnums import android.content.Context import com.android.settings.R import com.android.settings.overlay.FeatureFactory.Companion.featureFactory +import com.android.settingslib.spaprivileged.model.app.AppOps import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider @@ -35,9 +36,11 @@ class MediaManagementAppsListModel(context: Context) : AppOpPermissionListModel( override val pageTitleResId = R.string.media_management_apps_title override val switchTitleResId = R.string.media_management_apps_toggle_label override val footerResId = R.string.media_management_apps_description - override val appOp = AppOpsManager.OP_MANAGE_MEDIA + override val appOps = AppOps( + op = AppOpsManager.OP_MANAGE_MEDIA, + setModeByUid = true, + ) override val permission = Manifest.permission.MANAGE_MEDIA - override val setModeByUid = true override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) { super.setAllowed(record, newAllowed) diff --git a/src/com/android/settings/spa/app/specialaccess/MediaRoutingControl.kt b/src/com/android/settings/spa/app/specialaccess/MediaRoutingControl.kt index b9fb9b8cd9a..21b9400da0a 100644 --- a/src/com/android/settings/spa/app/specialaccess/MediaRoutingControl.kt +++ b/src/com/android/settings/spa/app/specialaccess/MediaRoutingControl.kt @@ -25,6 +25,7 @@ import android.content.Context import com.android.media.flags.Flags; import com.android.settings.R import com.android.settings.overlay.FeatureFactory.Companion.featureFactory +import com.android.settingslib.spaprivileged.model.app.AppOps import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider @@ -38,9 +39,11 @@ class MediaRoutingControlAppsListModel(context: Context) : AppOpPermissionListMo override val pageTitleResId = R.string.media_routing_control_title override val switchTitleResId = R.string.allow_media_routing_control override val footerResId = R.string.allow_media_routing_description - override val appOp = AppOpsManager.OP_MEDIA_ROUTING_CONTROL + override val appOps = AppOps( + op = AppOpsManager.OP_MEDIA_ROUTING_CONTROL, + setModeByUid = true, + ) override val permission = Manifest.permission.MEDIA_ROUTING_CONTROL - override val setModeByUid = true private val roleManager = context.getSystemService(RoleManager::class.java) override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) { diff --git a/src/com/android/settings/spa/app/specialaccess/ModifySystemSettings.kt b/src/com/android/settings/spa/app/specialaccess/ModifySystemSettings.kt index 4ab7f52cc29..e5f65f5a4b7 100644 --- a/src/com/android/settings/spa/app/specialaccess/ModifySystemSettings.kt +++ b/src/com/android/settings/spa/app/specialaccess/ModifySystemSettings.kt @@ -22,6 +22,7 @@ import android.app.settings.SettingsEnums import android.content.Context import com.android.settings.R import com.android.settings.overlay.FeatureFactory.Companion.featureFactory +import com.android.settingslib.spaprivileged.model.app.AppOps import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider @@ -35,7 +36,7 @@ class ModifySystemSettingsListModel(context: Context) : AppOpPermissionListModel override val pageTitleResId = R.string.write_system_settings override val switchTitleResId = R.string.permit_write_settings override val footerResId = R.string.write_settings_description - override val appOp = AppOpsManager.OP_WRITE_SETTINGS + override val appOps = AppOps(AppOpsManager.OP_WRITE_SETTINGS) override val permission = Manifest.permission.WRITE_SETTINGS override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) { diff --git a/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt b/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt index 7885b869b83..d767f737d21 100644 --- a/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt +++ b/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt @@ -17,7 +17,6 @@ package com.android.settings.spa.app.specialaccess import android.app.AppOpsManager -import android.app.AppOpsManager.OP_PICTURE_IN_PICTURE import android.content.Context import android.content.pm.ActivityInfo import android.content.pm.ApplicationInfo @@ -28,6 +27,7 @@ import android.util.Log import androidx.compose.runtime.Composable import com.android.settings.R import com.android.settingslib.spa.lifecycle.collectAsCallbackWithLifecycle +import com.android.settingslib.spaprivileged.model.app.AppOps import com.android.settingslib.spaprivileged.model.app.AppOpsController import com.android.settingslib.spaprivileged.model.app.AppRecord import com.android.settingslib.spaprivileged.model.app.installed @@ -79,12 +79,7 @@ class PictureInPictureListModel(private val context: Context) : PictureInPictureRecord( app = app, isSupport = isSupport, - appOpsController = - AppOpsController( - context = context, - app = app, - op = OP_PICTURE_IN_PICTURE, - ), + appOpsController = AppOpsController(context = context, app = app, appOps = APP_OPS), ) override fun filter(userIdFlow: Flow, recordListFlow: Flow>) = @@ -131,6 +126,8 @@ class PictureInPictureListModel(private val context: Context) : companion object { private const val TAG = "PictureInPictureListModel" + private val APP_OPS = AppOps(AppOpsManager.OP_PICTURE_IN_PICTURE) + private fun PackageInfo.supportsPictureInPicture() = activities?.any(ActivityInfo::supportsPictureInPicture) ?: false diff --git a/src/com/android/settings/spa/app/specialaccess/TurnScreenOnApps.kt b/src/com/android/settings/spa/app/specialaccess/TurnScreenOnApps.kt index 262acb78d15..db9fe448b5a 100644 --- a/src/com/android/settings/spa/app/specialaccess/TurnScreenOnApps.kt +++ b/src/com/android/settings/spa/app/specialaccess/TurnScreenOnApps.kt @@ -20,8 +20,8 @@ import android.Manifest import android.app.AppOpsManager import android.app.settings.SettingsEnums import android.content.Context -import com.android.settings.R import com.android.settings.overlay.FeatureFactory.Companion.featureFactory +import com.android.settingslib.spaprivileged.model.app.AppOps import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider @@ -35,9 +35,11 @@ class TurnScreenOnAppsListModel(context: Context) : AppOpPermissionListModel(con override val pageTitleResId = com.android.settingslib.R.string.turn_screen_on_title override val switchTitleResId = com.android.settingslib.R.string.allow_turn_screen_on override val footerResId = com.android.settingslib.R.string.allow_turn_screen_on_description - override val appOp = AppOpsManager.OP_TURN_SCREEN_ON + override val appOps = AppOps( + op = AppOpsManager.OP_TURN_SCREEN_ON, + setModeByUid = true, + ) override val permission = Manifest.permission.TURN_SCREEN_ON - override val setModeByUid = true override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) { super.setAllowed(record, newAllowed) diff --git a/src/com/android/settings/spa/app/specialaccess/UseFullScreenIntent.kt b/src/com/android/settings/spa/app/specialaccess/UseFullScreenIntent.kt index 514e480250c..cc41e63c779 100644 --- a/src/com/android/settings/spa/app/specialaccess/UseFullScreenIntent.kt +++ b/src/com/android/settings/spa/app/specialaccess/UseFullScreenIntent.kt @@ -20,6 +20,7 @@ import android.Manifest import android.app.AppOpsManager import android.content.Context import com.android.settings.R +import com.android.settingslib.spaprivileged.model.app.AppOps import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider @@ -32,7 +33,9 @@ class UseFullScreenIntentListModel(context: Context) : AppOpPermissionListModel( override val pageTitleResId = R.string.full_screen_intent_title override val switchTitleResId = R.string.permit_full_screen_intent override val footerResId = R.string.footer_description_full_screen_intent - override val appOp = AppOpsManager.OP_USE_FULL_SCREEN_INTENT + override val appOps = AppOps( + op = AppOpsManager.OP_USE_FULL_SCREEN_INTENT, + setModeByUid = true, + ) override val permission = Manifest.permission.USE_FULL_SCREEN_INTENT - override val setModeByUid = true } diff --git a/src/com/android/settings/spa/app/specialaccess/WifiControlApps.kt b/src/com/android/settings/spa/app/specialaccess/WifiControlApps.kt index 50bb5c9b9f6..c160569b46a 100644 --- a/src/com/android/settings/spa/app/specialaccess/WifiControlApps.kt +++ b/src/com/android/settings/spa/app/specialaccess/WifiControlApps.kt @@ -21,6 +21,7 @@ import android.app.AppOpsManager import android.app.AppOpsManager.MODE_IGNORED import android.content.Context import com.android.settings.R +import com.android.settingslib.spaprivileged.model.app.AppOps import com.android.settingslib.spaprivileged.model.app.IPackageManagers import com.android.settingslib.spaprivileged.model.app.PackageManagers import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel @@ -39,11 +40,13 @@ class WifiControlAppListModel( override val switchTitleResId = R.string.change_wifi_state_app_detail_switch override val footerResId = R.string.change_wifi_state_app_detail_summary - override val appOp = AppOpsManager.OP_CHANGE_WIFI_STATE + override val appOps = AppOps( + op = AppOpsManager.OP_CHANGE_WIFI_STATE, + modeForNotAllowed = MODE_IGNORED, + ) override val permission = Manifest.permission.CHANGE_WIFI_STATE /** NETWORK_SETTINGS permission trumps CHANGE_WIFI_CONFIG. */ override val broaderPermission = Manifest.permission.NETWORK_SETTINGS override val permissionHasAppOpFlag = false - override val modeForNotAllowed = MODE_IGNORED } diff --git a/tests/spa_unit/src/com/android/settings/spa/app/WifiControlAppListModelTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/WifiControlAppListModelTest.kt index 537764afe7c..863a6e2e97b 100644 --- a/tests/spa_unit/src/com/android/settings/spa/app/WifiControlAppListModelTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/app/WifiControlAppListModelTest.kt @@ -269,7 +269,7 @@ class WifiControlAppListModelTest { private class FakeAppOpsController(private val fakeMode: Int) : IAppOpsController { var setAllowedCalledWith: Boolean? = null - override val mode = flowOf(fakeMode) + override val modeFlow = flowOf(fakeMode) override fun setAllowed(allowed: Boolean) { setAllowedCalledWith = allowed diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/AllFilesAccessTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/AllFilesAccessTest.kt index 4c65d90a627..bf48b841b88 100644 --- a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/AllFilesAccessTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/AllFilesAccessTest.kt @@ -22,6 +22,7 @@ import android.content.Context import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.R +import com.android.settingslib.spaprivileged.model.app.AppOps import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -37,8 +38,12 @@ class AllFilesAccessTest { assertThat(listModel.pageTitleResId).isEqualTo(R.string.manage_external_storage_title) assertThat(listModel.switchTitleResId).isEqualTo(R.string.permit_manage_external_storage) assertThat(listModel.footerResId).isEqualTo(R.string.allow_manage_external_storage_description) - assertThat(listModel.appOp).isEqualTo(AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE) + assertThat(listModel.appOps).isEqualTo( + AppOps( + op = AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE, + setModeByUid = true, + ) + ) assertThat(listModel.permission).isEqualTo(Manifest.permission.MANAGE_EXTERNAL_STORAGE) - assertThat(listModel.setModeByUid).isTrue() } } \ No newline at end of file diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/LongBackgroundTasksAppsTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/LongBackgroundTasksAppsTest.kt index 579c6c95508..dc56ddf856d 100644 --- a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/LongBackgroundTasksAppsTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/LongBackgroundTasksAppsTest.kt @@ -23,6 +23,7 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import com.android.settings.R +import com.android.settingslib.spaprivileged.model.app.AppOps import org.junit.Test import org.junit.runner.RunWith @@ -37,8 +38,12 @@ class LongBackgroundTasksAppsTest { assertThat(listModel.pageTitleResId).isEqualTo(R.string.long_background_tasks_title) assertThat(listModel.switchTitleResId).isEqualTo(R.string.long_background_tasks_switch_title) assertThat(listModel.footerResId).isEqualTo(R.string.long_background_tasks_footer_title) - assertThat(listModel.appOp).isEqualTo(AppOpsManager.OP_RUN_USER_INITIATED_JOBS) + assertThat(listModel.appOps).isEqualTo( + AppOps( + op = AppOpsManager.OP_RUN_USER_INITIATED_JOBS, + setModeByUid = true, + ) + ) assertThat(listModel.permission).isEqualTo(Manifest.permission.RUN_USER_INITIATED_JOBS) - assertThat(listModel.setModeByUid).isTrue() } } \ No newline at end of file diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/MediaManagementAppsTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/MediaManagementAppsTest.kt index b901043f59f..70b4b48867b 100644 --- a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/MediaManagementAppsTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/MediaManagementAppsTest.kt @@ -22,6 +22,7 @@ import android.content.Context import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.R +import com.android.settingslib.spaprivileged.model.app.AppOps import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -37,8 +38,12 @@ class MediaManagementAppsTest { assertThat(listModel.pageTitleResId).isEqualTo(R.string.media_management_apps_title) assertThat(listModel.switchTitleResId).isEqualTo(R.string.media_management_apps_toggle_label) assertThat(listModel.footerResId).isEqualTo(R.string.media_management_apps_description) - assertThat(listModel.appOp).isEqualTo(AppOpsManager.OP_MANAGE_MEDIA) + assertThat(listModel.appOps).isEqualTo( + AppOps( + op = AppOpsManager.OP_MANAGE_MEDIA, + setModeByUid = true, + ) + ) assertThat(listModel.permission).isEqualTo(Manifest.permission.MANAGE_MEDIA) - assertThat(listModel.setModeByUid).isTrue() } } \ No newline at end of file diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/MediaRoutingControlTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/MediaRoutingControlTest.kt index 990ec5ce417..ec070fdde05 100644 --- a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/MediaRoutingControlTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/MediaRoutingControlTest.kt @@ -29,6 +29,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.media.flags.Flags import com.android.settings.R import com.android.settings.testutils.FakeFeatureFactory +import com.android.settingslib.spaprivileged.model.app.AppOps import com.android.settingslib.spaprivileged.model.app.IAppOpsController import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord import com.google.common.truth.Truth.assertThat @@ -74,9 +75,13 @@ class MediaRoutingControlTest { assertThat(listModel.pageTitleResId).isEqualTo(R.string.media_routing_control_title) assertThat(listModel.switchTitleResId).isEqualTo(R.string.allow_media_routing_control) assertThat(listModel.footerResId).isEqualTo(R.string.allow_media_routing_description) - assertThat(listModel.appOp).isEqualTo(AppOpsManager.OP_MEDIA_ROUTING_CONTROL) + assertThat(listModel.appOps).isEqualTo( + AppOps( + op = AppOpsManager.OP_MEDIA_ROUTING_CONTROL, + setModeByUid = true, + ) + ) assertThat(listModel.permission).isEqualTo(Manifest.permission.MEDIA_ROUTING_CONTROL) - assertThat(listModel.setModeByUid).isTrue() } @Test @@ -223,13 +228,13 @@ class MediaRoutingControlTest { private class FakeAppOpsController(fakeMode: Int) : IAppOpsController { - override val mode = MutableStateFlow(fakeMode) + override val modeFlow = MutableStateFlow(fakeMode) override fun setAllowed(allowed: Boolean) { - mode.value = if (allowed) AppOpsManager.MODE_ALLOWED else AppOpsManager.MODE_ERRORED + modeFlow.value = if (allowed) AppOpsManager.MODE_ALLOWED else AppOpsManager.MODE_ERRORED } - override fun getMode(): Int = mode.value + override fun getMode(): Int = modeFlow.value } companion object { diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt index 42292478f56..6e41e92b1b0 100644 --- a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt @@ -27,6 +27,7 @@ import android.os.DeadSystemRuntimeException import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.R +import com.android.settingslib.spaprivileged.model.app.AppOps import com.android.settingslib.spaprivileged.model.app.AppOpsController import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.first @@ -179,7 +180,7 @@ class PictureInPictureTest { appOpsController = AppOpsController( context = context, app = PICTURE_IN_PICTURE_APP, - op = AppOpsManager.OP_PICTURE_IN_PICTURE, + appOps = AppOps(AppOpsManager.OP_PICTURE_IN_PICTURE), ), ) diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/TurnScreenOnAppsTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/TurnScreenOnAppsTest.kt index 9c6079d5eca..1b1204f31e8 100644 --- a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/TurnScreenOnAppsTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/TurnScreenOnAppsTest.kt @@ -20,6 +20,7 @@ import android.app.AppOpsManager import android.content.Context import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spaprivileged.model.app.AppOps import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -35,8 +36,12 @@ class TurnScreenOnAppsTest { assertThat(listModel.pageTitleResId).isEqualTo(com.android.settingslib.R.string.turn_screen_on_title) assertThat(listModel.switchTitleResId).isEqualTo(com.android.settingslib.R.string.allow_turn_screen_on) assertThat(listModel.footerResId).isEqualTo(com.android.settingslib.R.string.allow_turn_screen_on_description) - assertThat(listModel.appOp).isEqualTo(AppOpsManager.OP_TURN_SCREEN_ON) + assertThat(listModel.appOps).isEqualTo( + AppOps( + op = AppOpsManager.OP_TURN_SCREEN_ON, + setModeByUid = true, + ) + ) assertThat(listModel.permission).isEqualTo(Manifest.permission.TURN_SCREEN_ON) - assertThat(listModel.setModeByUid).isTrue() } } \ No newline at end of file From ec8ce02ba4f3507a65a1a6840c81ef35126f3b4a Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Mon, 20 May 2024 11:38:58 +0800 Subject: [PATCH 07/17] Sort selectable subscriptions by sim slot index To match the sorting order in SubscriptionManagerService.getAvailableSubscriptionInfoList. Fix: 340361264 Test: manual - on SIMs Test: unit test Change-Id: I06b6c61304f9e6c9515b5f989294417ac3f82a60 --- .../telephony/SubscriptionRepository.kt | 28 ++++++------ .../telephony/SubscriptionRepositoryTest.kt | 44 +++++++++++-------- 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/src/com/android/settings/network/telephony/SubscriptionRepository.kt b/src/com/android/settings/network/telephony/SubscriptionRepository.kt index adb67e1a512..4fd28a20f93 100644 --- a/src/com/android/settings/network/telephony/SubscriptionRepository.kt +++ b/src/com/android/settings/network/telephony/SubscriptionRepository.kt @@ -109,21 +109,21 @@ fun Context.getSelectableSubscriptionInfoList(): List { // to users so they should never be returned. SubscriptionUtil.isSubscriptionVisible(subscriptionManager, this, subInfo) } - // Multiple subscriptions in a group should only have one representative. - // It should be the current active primary subscription if any, or any primary subscription. - val groupUuidToSelectedIdMap = visibleList - .groupBy { it.groupUuid } - .mapValues { (_, subInfos) -> - subInfos.filter { it.simSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX } - .ifEmpty { subInfos } - .minOf { it.subscriptionId } - } - return visibleList - .filter { subInfo -> - val groupUuid = subInfo.groupUuid ?: return@filter true - groupUuidToSelectedIdMap[groupUuid] == subInfo.subscriptionId + .groupBy { it.groupUuid } + .flatMap { (groupUuid, subInfos) -> + if (groupUuid == null) { + subInfos + } else { + // Multiple subscriptions in a group should only have one representative. + // It should be the current active primary subscription if any, or the primary + // subscription with minimum subscription id. + subInfos.filter { it.simSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX } + .ifEmpty { subInfos.sortedBy { it.subscriptionId } } + .take(1) + } } - .sortedBy { it.subscriptionId } + // Matching the sorting order in SubscriptionManagerService.getAvailableSubscriptionInfoList + .sortedWith(compareBy({ it.simSlotIndex }, { it.subscriptionId })) .also { Log.d(TAG, "getSelectableSubscriptionInfoList: $it") } } diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt index ce160979837..b2ee018faf0 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt @@ -66,10 +66,11 @@ class SubscriptionRepositoryTest { @Test fun isSubscriptionEnabledFlow_enabled() = runBlocking { mockSubscriptionManager.stub { - on { isSubscriptionEnabled(SUB_ID_1) } doReturn true + on { isSubscriptionEnabled(SUB_ID_IN_SLOT_0) } doReturn true } - val isEnabled = repository.isSubscriptionEnabledFlow(SUB_ID_1).firstWithTimeoutOrNull() + val isEnabled = + repository.isSubscriptionEnabledFlow(SUB_ID_IN_SLOT_0).firstWithTimeoutOrNull() assertThat(isEnabled).isTrue() } @@ -94,21 +95,24 @@ class SubscriptionRepositoryTest { } @Test - fun getSelectableSubscriptionInfoList_sortedBySubId() { + fun getSelectableSubscriptionInfoList_sortedBySimSlotIndex() { mockSubscriptionManager.stub { on { getAvailableSubscriptionInfoList() } doReturn listOf( SubscriptionInfo.Builder().apply { - setId(SUB_ID_2) + setSimSlotIndex(SIM_SLOT_INDEX_0) + setId(SUB_ID_IN_SLOT_0) }.build(), SubscriptionInfo.Builder().apply { - setId(SUB_ID_1) + setSimSlotIndex(SIM_SLOT_INDEX_1) + setId(SUB_ID_IN_SLOT_1) }.build(), ) } val subInfos = context.getSelectableSubscriptionInfoList() - assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_1, SUB_ID_2).inOrder() + assertThat(subInfos.map { it.simSlotIndex }) + .containsExactly(SIM_SLOT_INDEX_0, SIM_SLOT_INDEX_1).inOrder() } @Test @@ -116,20 +120,21 @@ class SubscriptionRepositoryTest { mockSubscriptionManager.stub { on { getAvailableSubscriptionInfoList() } doReturn listOf( SubscriptionInfo.Builder().apply { - setId(SUB_ID_1) + setSimSlotIndex(SubscriptionManager.INVALID_SIM_SLOT_INDEX) + setId(SUB_ID_3_NOT_IN_SLOT) setGroupUuid(GROUP_UUID) }.build(), SubscriptionInfo.Builder().apply { - setId(SUB_ID_2) + setSimSlotIndex(SIM_SLOT_INDEX_0) + setId(SUB_ID_IN_SLOT_0) setGroupUuid(GROUP_UUID) - setSimSlotIndex(SIM_SLOT_INDEX) }.build(), ) } val subInfos = context.getSelectableSubscriptionInfoList() - assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_2) + assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_IN_SLOT_0) } @Test @@ -137,11 +142,11 @@ class SubscriptionRepositoryTest { mockSubscriptionManager.stub { on { getAvailableSubscriptionInfoList() } doReturn listOf( SubscriptionInfo.Builder().apply { - setId(SUB_ID_2) + setId(SUB_ID_4_NOT_IN_SLOT) setGroupUuid(GROUP_UUID) }.build(), SubscriptionInfo.Builder().apply { - setId(SUB_ID_1) + setId(SUB_ID_3_NOT_IN_SLOT) setGroupUuid(GROUP_UUID) }.build(), ) @@ -149,16 +154,16 @@ class SubscriptionRepositoryTest { val subInfos = context.getSelectableSubscriptionInfoList() - assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_1) + assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_3_NOT_IN_SLOT) } @Test fun phoneNumberFlow() = runBlocking { mockSubscriptionManager.stub { - on { getPhoneNumber(SUB_ID_1) } doReturn NUMBER_1 + on { getPhoneNumber(SUB_ID_IN_SLOT_1) } doReturn NUMBER_1 } val subInfo = SubscriptionInfo.Builder().apply { - setId(SUB_ID_1) + setId(SUB_ID_IN_SLOT_1) setMcc(MCC) }.build() @@ -168,10 +173,13 @@ class SubscriptionRepositoryTest { } private companion object { - const val SUB_ID_1 = 1 - const val SUB_ID_2 = 2 + const val SIM_SLOT_INDEX_0 = 0 + const val SUB_ID_IN_SLOT_0 = 2 + const val SIM_SLOT_INDEX_1 = 1 + const val SUB_ID_IN_SLOT_1 = 1 + const val SUB_ID_3_NOT_IN_SLOT = 3 + const val SUB_ID_4_NOT_IN_SLOT = 4 val GROUP_UUID = UUID.randomUUID().toString() - const val SIM_SLOT_INDEX = 1 const val NUMBER_1 = "000000001" const val MCC = "310" } From f6b5904511bd405b31fedd7bda92b2fd18fc255b Mon Sep 17 00:00:00 2001 From: Faye Yan Date: Thu, 4 Apr 2024 03:40:46 +0000 Subject: [PATCH 08/17] Revert "Add Voice activation apps into Settings->Apps->Specific app->AppInfo->Voice activation apps" This reverts commit 7f2fbc2e4cdc08a6b23939ec4de1ef4989706041. Reason for revert: voice activation is not going in V Test: manual - on Special app access Change-Id: Idfabfd48f45d2ace7de9f8498181f281ad0358a1 Merged-In: Idfabfd48f45d2ace7de9f8498181f281ad0358a1 --- src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt index 3b2fdcfa7da..f38dd96ef98 100644 --- a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt +++ b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt @@ -43,7 +43,6 @@ import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListPro import com.android.settings.spa.app.specialaccess.InstallUnknownAppsListProvider import com.android.settings.spa.app.specialaccess.ModifySystemSettingsAppListProvider import com.android.settings.spa.app.specialaccess.PictureInPictureListProvider -import com.android.settings.spa.app.specialaccess.VoiceActivationAppsListProvider import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.compose.navigator import com.android.settingslib.spa.widget.scaffold.RegularScaffold @@ -167,9 +166,6 @@ private fun AppInfoSettings(packageInfoPresenter: PackageInfoPresenter) { InstallUnknownAppsListProvider.InfoPageEntryItem(app) InteractAcrossProfilesDetailsPreference(app) AlarmsAndRemindersAppListProvider.InfoPageEntryItem(app) - if (Flags.enableVoiceActivationAppsInSettings()) { - VoiceActivationAppsListProvider.InfoPageEntryItem(app) - } if (Flags.enablePerformBackupTasksInSettings()) { BackupTasksAppsListProvider.InfoPageEntryItem(app) } From a7065f4d36328cb08092e65b8ac8662aa7a6d119 Mon Sep 17 00:00:00 2001 From: Faye Yan Date: Thu, 4 Apr 2024 02:29:19 +0000 Subject: [PATCH 09/17] Revert "Add Voice activation apps into Settings->Apps->Special a..." Revert submission 25108596-va-special-access Reason for revert: voice activation is not going in V Reverted changes: /q/submissionid:25108596-va-special-access Bug: 306447565 Bug: 393727896 Test: presubmit Change-Id: I4977e7d543cb800fc9fb6962bba3afebb9480dcc Merged-In: I4977e7d543cb800fc9fb6962bba3afebb9480dcc --- res/values/strings.xml | 7 -- res/xml/special_access.xml | 5 -- .../android/settings/SettingsActivityUtil.kt | 3 - .../settings/spa/SettingsSpaEnvironment.kt | 2 - .../spa/app/specialaccess/SpecialAppAccess.kt | 1 - .../app/specialaccess/VoiceActivationApps.kt | 67 ------------------- ...VoiceActivationAppsPreferenceController.kt | 38 ----------- ...eActivationAppsPreferenceControllerTest.kt | 65 ------------------ .../specialaccess/VoiceActivationAppsTest.kt | 30 --------- 9 files changed, 218 deletions(-) delete mode 100644 src/com/android/settings/spa/app/specialaccess/VoiceActivationApps.kt delete mode 100644 src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsPreferenceController.kt delete mode 100644 tests/spa_unit/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsPreferenceControllerTest.kt delete mode 100644 tests/spa_unit/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsTest.kt diff --git a/res/values/strings.xml b/res/values/strings.xml index d51a692c008..ff8339b2800 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -9703,13 +9703,6 @@ Can access all files - - Voice activation apps - - Allow voice activation - - Voice activation turns-on approved apps, hands-free, using voice command. Built-in adaptive sensing ensures data stays private only to you.\n\nMore about protected adaptive sensing - Full screen notifications diff --git a/res/xml/special_access.xml b/res/xml/special_access.xml index d522ef60b61..8f98fd9b86c 100644 --- a/res/xml/special_access.xml +++ b/res/xml/special_access.xml @@ -114,11 +114,6 @@ android:title="@string/full_screen_intent_title" settings:controller="com.android.settings.spa.app.specialaccess.UseFullScreenIntentPreferenceController" /> - - SettingsEnums.APP_SPECIAL_PERMISSION_RECEIVE_SANDBOX_TRIGGER_AUDIO_ALLOW - else -> SettingsEnums.APP_SPECIAL_PERMISSION_RECEIVE_SANDBOX_TRIGGER_AUDIO_DENY - } - /** - * Leave the package string empty as we should not log the package names for the collected - * metrics. - */ - FeatureFactory.featureFactory.metricsFeatureProvider.action(context, category, "") - } -} \ No newline at end of file diff --git a/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsPreferenceController.kt b/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsPreferenceController.kt deleted file mode 100644 index 27d4b4b9035..00000000000 --- a/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsPreferenceController.kt +++ /dev/null @@ -1,38 +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.spa.app.specialaccess - -import android.content.Context -import androidx.preference.Preference -import com.android.settings.core.BasePreferenceController -import com.android.settings.flags.Flags -import com.android.settings.spa.SpaActivity.Companion.startSpaActivity - -class VoiceActivationAppsPreferenceController(context: Context, preferenceKey: String) : - BasePreferenceController(context, preferenceKey) { - override fun getAvailabilityStatus() = - if (Flags.enableVoiceActivationAppsInSettings()) AVAILABLE - else CONDITIONALLY_UNAVAILABLE - - override fun handlePreferenceTreeClick(preference: Preference): Boolean { - if (preference.key == mPreferenceKey) { - mContext.startSpaActivity(VoiceActivationAppsListProvider.getAppListRoute()) - return true - } - return false - } -} \ No newline at end of file diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsPreferenceControllerTest.kt deleted file mode 100644 index 2127497ded8..00000000000 --- a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsPreferenceControllerTest.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.android.settings.spa.app.specialaccess - -import android.content.Context -import android.platform.test.annotations.RequiresFlagsDisabled -import android.platform.test.annotations.RequiresFlagsEnabled -import android.platform.test.flag.junit.CheckFlagsRule -import android.platform.test.flag.junit.DeviceFlagsValueProvider -import androidx.preference.Preference -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import com.android.settings.flags.Flags -import com.google.common.truth.Truth.assertThat - -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.kotlin.any -import org.mockito.kotlin.doNothing -import org.mockito.kotlin.spy -import org.mockito.kotlin.whenever - -@RunWith(AndroidJUnit4::class) -class VoiceActivationAppsPreferenceControllerTest { - - @get:Rule - val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() - - private val context: Context = spy(ApplicationProvider.getApplicationContext()) { - doNothing().whenever(mock).startActivity(any()) - } - - private val matchedPreference = Preference(context).apply { key = preferenceKey } - - private val misMatchedPreference = Preference(context).apply { key = testPreferenceKey } - - private val controller = VoiceActivationAppsPreferenceController(context, preferenceKey) - - @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_VOICE_ACTIVATION_APPS_IN_SETTINGS) - fun getAvailabilityStatus_enableVoiceActivationApps_returnAvailable() { - assertThat(controller.isAvailable).isTrue() - } - - @Test - @RequiresFlagsDisabled(Flags.FLAG_ENABLE_VOICE_ACTIVATION_APPS_IN_SETTINGS) - fun getAvailableStatus_disableVoiceActivationApps_returnConditionallyUnavailable() { - assertThat(controller.isAvailable).isFalse() - } - - @Test - fun handlePreferenceTreeClick_keyMatched_returnTrue() { - assertThat(controller.handlePreferenceTreeClick(matchedPreference)).isTrue() - } - - @Test - fun handlePreferenceTreeClick_keyMisMatched_returnFalse() { - assertThat(controller.handlePreferenceTreeClick(misMatchedPreference)).isFalse() - } - - companion object { - private const val preferenceKey: String = "voice_activation_apps" - private const val testPreferenceKey: String = "test_key" - } -} \ No newline at end of file diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsTest.kt deleted file mode 100644 index a2aa293731a..00000000000 --- a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsTest.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.android.settings.spa.app.specialaccess - -import android.Manifest -import android.app.AppOpsManager -import android.content.Context -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.settings.R -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class VoiceActivationAppsTest { - private val context: Context = ApplicationProvider.getApplicationContext() - - private val listModel = VoiceActivationAppsListModel(context) - - @Test - fun modelResourceIdAndProperties() { - assertThat(listModel.pageTitleResId).isEqualTo(R.string.voice_activation_apps_title) - assertThat(listModel.switchTitleResId).isEqualTo(R.string.permit_voice_activation_apps) - assertThat(listModel.footerResId).isEqualTo(R.string.allow_voice_activation_apps_description) - assertThat(listModel.appOp).isEqualTo(AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO) - assertThat(listModel.permission).isEqualTo( - Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO - ) - assertThat(listModel.setModeByUid).isTrue() - } -} \ No newline at end of file From c8b89dde096bcf775bc94f012f54ba24a0171abe Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Mon, 20 May 2024 13:34:38 +0800 Subject: [PATCH 10/17] Fix CdmaSystemSelectPreferenceControllerTest Fix: 337417917 Test: unit test Change-Id: If215f04ad09c44a3ef7d89b0bac5116026463e4e --- .../cdma/CdmaSystemSelectPreferenceControllerTest.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/unit/src/com/android/settings/network/telephony/cdma/CdmaSystemSelectPreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/cdma/CdmaSystemSelectPreferenceControllerTest.java index 2b886c19eab..c028b8cbbca 100644 --- a/tests/unit/src/com/android/settings/network/telephony/cdma/CdmaSystemSelectPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/network/telephony/cdma/CdmaSystemSelectPreferenceControllerTest.java @@ -38,7 +38,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -66,6 +65,7 @@ public class CdmaSystemSelectPreferenceControllerTest { mContext = spy(ApplicationProvider.getApplicationContext()); when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(SUB_ID); + when(mTelephonyManager.getPhoneType()).thenReturn(TelephonyManager.PHONE_TYPE_CDMA); mPreference = new ListPreference(mContext); mController = new CdmaSystemSelectPreferenceController(mContext, "mobile_data"); @@ -100,7 +100,6 @@ public class CdmaSystemSelectPreferenceControllerTest { } @Test - @Ignore("b/337417544") public void updateState_stateHome_displayHome() { doReturn(TelephonyManager.CDMA_ROAMING_MODE_HOME).when( mTelephonyManager).getCdmaRoamingMode(); @@ -112,7 +111,6 @@ public class CdmaSystemSelectPreferenceControllerTest { } @Test - @Ignore("b/337417897") public void updateState_LteGSMWcdma_disabled() { doReturn(TelephonyManager.CDMA_ROAMING_MODE_HOME).when( mTelephonyManager).getCdmaRoamingMode(); @@ -126,7 +124,6 @@ public class CdmaSystemSelectPreferenceControllerTest { } @Test - @Ignore("b/337417917") public void updateState_stateOther_resetToDefault() { Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.CDMA_ROAMING_MODE, From 66c16d57af59d5541725f9560f9eceb04db79fd1 Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Thu, 16 May 2024 12:06:59 +0000 Subject: [PATCH 11/17] Update Search Bar layout in Homepage Bug: 340513864 Bug: 340773713 Bug: 340799397 Bug: 340759836 Test: visual Change-Id: I4bd5c3438aa55ca764f61b180a37c10206ee78be --- res/layout/search_bar_unified_version.xml | 36 +++++++++++-------- ...ttings_homepage_app_bar_unified_layout.xml | 29 --------------- res/layout/settings_homepage_container_v2.xml | 14 ++++---- res/values/styles.xml | 7 ++++ res/values/themes.xml | 1 + .../homepage/SettingsHomepageActivity.java | 6 +++- .../search/SearchFeatureProvider.java | 18 ++++++---- 7 files changed, 53 insertions(+), 58 deletions(-) delete mode 100644 res/layout/settings_homepage_app_bar_unified_layout.xml diff --git a/res/layout/search_bar_unified_version.xml b/res/layout/search_bar_unified_version.xml index eec8406af7d..a8ad6fc4033 100644 --- a/res/layout/search_bar_unified_version.xml +++ b/res/layout/search_bar_unified_version.xml @@ -17,28 +17,34 @@ - + + + android:background="@drawable/search_bar_selected_background"> + + - + diff --git a/res/layout/settings_homepage_app_bar_unified_layout.xml b/res/layout/settings_homepage_app_bar_unified_layout.xml deleted file mode 100644 index 3e254186822..00000000000 --- a/res/layout/settings_homepage_app_bar_unified_layout.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - diff --git a/res/layout/settings_homepage_container_v2.xml b/res/layout/settings_homepage_container_v2.xml index 55fb1dac57f..c3ac1ab107d 100644 --- a/res/layout/settings_homepage_container_v2.xml +++ b/res/layout/settings_homepage_container_v2.xml @@ -62,19 +62,21 @@ android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="wrap_content" + app:elevation="0dp" android:touchscreenBlocksFocus="false" android:keyboardNavigationCluster="false"> + android:orientation="horizontal" + android:paddingTop="8dp" + android:paddingBottom="24dp" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + + - diff --git a/res/values/styles.xml b/res/values/styles.xml index f7d7a9043b1..54e2fa2b496 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -459,6 +459,13 @@ 0dp + +