summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/content/pm/CrossProfileApps.java9
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java22
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java1
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDeviceImpl.java7
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintManager.java51
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintServiceReceiver.java5
-rw-r--r--core/java/android/hardware/fingerprint/IFingerprintService.aidl2
-rw-r--r--core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl1
-rw-r--r--core/java/android/os/FileUtils.java24
-rw-r--r--core/java/android/os/storage/IStorageManager.aidl1
-rw-r--r--core/java/android/os/storage/StorageManager.java9
-rw-r--r--core/java/android/provider/CallLog.java18
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java2
-rw-r--r--core/res/res/layout/shutdown_dialog.xml2
-rw-r--r--core/res/res/values/config.xml1
-rw-r--r--core/res/res/values/config_telephony.xml9
-rw-r--r--core/res/res/values/strings.xml8
-rw-r--r--core/res/res/values/symbols.xml5
-rw-r--r--core/tests/coretests/src/android/os/FileUtilsTest.java65
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java8
-rw-r--r--packages/SettingsLib/IllustrationPreference/res/values/colors.xml2
-rw-r--r--packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java9
-rw-r--r--packages/SettingsLib/Spa/tests/Android.bp1
-rw-r--r--packages/SettingsLib/Spa/testutils/Android.bp1
-rw-r--r--packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicSummary.java2
-rw-r--r--packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicTitle.java2
-rw-r--r--packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntriesProvider.java222
-rw-r--r--packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntryController.java246
-rw-r--r--packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderIcon.java2
-rw-r--r--packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderSwitch.java41
-rw-r--r--packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderTile.java2
-rw-r--r--packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchController.java217
-rw-r--r--packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java165
-rw-r--r--packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java89
-rw-r--r--packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java55
-rw-r--r--packages/SettingsLib/res/values-lt/strings.xml24
-rw-r--r--packages/SettingsLib/res/values-zh-rCN/strings.xml24
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java19
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java7
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java20
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java65
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/EntriesProviderTest.java472
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java86
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java51
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml1
-rw-r--r--packages/SystemUI/res/layout/biometric_prompt_layout.xml1
-rw-r--r--packages/SystemUI/res/values/flags.xml5
-rw-r--r--packages/SystemUI/res/values/strings.xml9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt11
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java7
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepository.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt88
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt (renamed from packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModality.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt113
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt126
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java117
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java164
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUiModule.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/ShadeTouchLog.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java84
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/TouchLogger.kt78
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java109
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java127
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java76
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt111
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt124
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt130
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt159
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java85
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt34
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt16
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFaceSettingsRepository.kt37
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt11
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt10
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java13
-rw-r--r--services/core/java/com/android/server/app/GameManagerService.java2
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java7
-rw-r--r--services/core/java/com/android/server/audio/BtHelper.java5
-rw-r--r--services/core/java/com/android/server/biometrics/AuthSession.java56
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricCameraManager.java32
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java68
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricService.java65
-rw-r--r--services/core/java/com/android/server/biometrics/PreAuthInfo.java66
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java8
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java8
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java6
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java10
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java5
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java7
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java15
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java7
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java5
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java7
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java2
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java27
-rw-r--r--services/core/java/com/android/server/display/BrightnessRangeController.java128
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java95
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java90
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController2.java104
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java12
-rw-r--r--services/core/java/com/android/server/display/NormalBrightnessModeController.java103
-rw-r--r--services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java2
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java2
-rw-r--r--services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java49
-rw-r--r--services/core/java/com/android/server/powerstats/PowerStatsLogger.java20
-rw-r--r--services/core/java/com/android/server/powerstats/PowerStatsService.java28
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java23
-rw-r--r--services/core/java/com/android/server/wm/BLASTSyncEngine.java5
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java12
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java87
-rw-r--r--services/core/xsd/display-device-config/display-device-config.xsd58
-rw-r--r--services/core/xsd/display-device-config/schema/current.txt34
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java10
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java17
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java18
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java4
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java4
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java67
-rw-r--r--services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_false.xml10
-rw-r--r--services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_true.xml10
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java42
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java154
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java167
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java55
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java107
-rw-r--r--services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java122
-rw-r--r--services/tests/servicestests/src/com/android/server/display/NormalBrightnessModeControllerTest.java116
-rw-r--r--services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java12
-rw-r--r--services/usage/java/com/android/server/usage/StorageStatsService.java27
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java9
-rw-r--r--tests/UiBench/Android.bp1
-rw-r--r--tests/UiBench/AndroidManifest.xml1
-rw-r--r--tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java67
204 files changed, 5832 insertions, 1350 deletions
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index c3df17d4b53e..529363f828bb 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -344,15 +344,22 @@ public class CrossProfileApps {
// If there is a label for the launcher intent, then use that as it is typically shorter.
// Otherwise, just use the top-level application name.
Intent launchIntent = pm.getLaunchIntentForPackage(mContext.getPackageName());
+ if (launchIntent == null) {
+ return getDefaultCallingApplicationLabel();
+ }
List<ResolveInfo> infos =
pm.queryIntentActivities(
launchIntent, PackageManager.ResolveInfoFlags.of(MATCH_DEFAULT_ONLY));
if (infos.size() > 0) {
return infos.get(0).loadLabel(pm);
}
+ return getDefaultCallingApplicationLabel();
+ }
+
+ private CharSequence getDefaultCallingApplicationLabel() {
return mContext.getApplicationInfo()
.loadSafeLabel(
- pm,
+ mContext.getPackageManager(),
/* ellipsizeDip= */ 0,
TextUtils.SAFE_STRING_FLAG_SINGLE_LINE
| TextUtils.SAFE_STRING_FLAG_TRIM);
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index a4dd0655917e..d352be16ae0c 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -212,14 +212,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
@GuardedBy("mLock")
private boolean mFoldedDeviceState;
- private final CameraManager.DeviceStateListener mFoldStateListener =
- new CameraManager.DeviceStateListener() {
- @Override
- public final void onDeviceStateChanged(boolean folded) {
- synchronized (mLock) {
- mFoldedDeviceState = folded;
- }
- }};
+ private CameraManager.DeviceStateListener mFoldStateListener;
private static final String TAG = "CameraCharacteristics";
@@ -245,7 +238,18 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
/**
* Return the device state listener for this Camera characteristics instance
*/
- CameraManager.DeviceStateListener getDeviceStateListener() { return mFoldStateListener; }
+ CameraManager.DeviceStateListener getDeviceStateListener() {
+ if (mFoldStateListener == null) {
+ mFoldStateListener = new CameraManager.DeviceStateListener() {
+ @Override
+ public final void onDeviceStateChanged(boolean folded) {
+ synchronized (mLock) {
+ mFoldedDeviceState = folded;
+ }
+ }};
+ }
+ return mFoldStateListener;
+ }
/**
* Overrides the property value
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 2a120d2043f0..a64d66fade25 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -1839,6 +1839,7 @@ public final class CameraManager {
ctx.getSystemService(DeviceStateManager.class).registerCallback(
new HandlerExecutor(mDeviceStateHandler), mFoldStateListener);
} catch (IllegalStateException e) {
+ mFoldStateListener = null;
Log.v(TAG, "Failed to register device state listener!");
Log.v(TAG, "Device state dependent characteristics updates will not be" +
"functional!");
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index e23bbc6c347d..d3bde4b4b8a8 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -20,6 +20,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainRunna
import android.annotation.NonNull;
import android.content.Context;
+import android.graphics.ImageFormat;
import android.hardware.ICameraService;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
@@ -1478,6 +1479,12 @@ public class CameraDeviceImpl extends CameraDevice
}
}
+ // Allow RAW formats, even when not advertised.
+ if (inputFormat == ImageFormat.RAW_PRIVATE || inputFormat == ImageFormat.RAW10
+ || inputFormat == ImageFormat.RAW12 || inputFormat == ImageFormat.RAW_SENSOR) {
+ return true;
+ }
+
if (validFormat == false) {
return false;
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 01977f6195ff..619544366b02 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -103,6 +103,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
private static final int MSG_UDFPS_POINTER_DOWN = 108;
private static final int MSG_UDFPS_POINTER_UP = 109;
private static final int MSG_POWER_BUTTON_PRESSED = 110;
+ private static final int MSG_UDFPS_OVERLAY_SHOWN = 111;
/**
* @hide
@@ -121,6 +122,24 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
public @interface EnrollReason {}
/**
+ * Udfps ui event of overlay is shown on the screen.
+ * @hide
+ */
+ public static final int UDFPS_UI_OVERLAY_SHOWN = 1;
+ /**
+ * Udfps ui event of the udfps UI being ready (e.g. HBM illumination is enabled).
+ * @hide
+ */
+ public static final int UDFPS_UI_READY = 2;
+
+ /**
+ * @hide
+ */
+ @IntDef({UDFPS_UI_OVERLAY_SHOWN, UDFPS_UI_READY})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UdfpsUiEvent{}
+
+ /**
* Request authentication with any single sensor.
* @hide
*/
@@ -475,12 +494,17 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
/**
* Called when a pointer down event has occurred.
*/
- public void onPointerDown(int sensorId){ }
+ public void onUdfpsPointerDown(int sensorId){ }
/**
* Called when a pointer up event has occurred.
*/
- public void onPointerUp(int sensorId){ }
+ public void onUdfpsPointerUp(int sensorId){ }
+
+ /**
+ * Called when udfps overlay is shown.
+ */
+ public void onUdfpsOverlayShown() { }
}
/**
@@ -1112,14 +1136,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
- public void onUiReady(long requestId, int sensorId) {
+ public void onUdfpsUiEvent(@UdfpsUiEvent int event, long requestId, int sensorId) {
if (mService == null) {
- Slog.w(TAG, "onUiReady: no fingerprint service");
+ Slog.w(TAG, "onUdfpsUiEvent: no fingerprint service");
return;
}
try {
- mService.onUiReady(requestId, sensorId);
+ mService.onUdfpsUiEvent(event, requestId, sensorId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1365,6 +1389,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
case MSG_POWER_BUTTON_PRESSED:
sendPowerPressed();
break;
+ case MSG_UDFPS_OVERLAY_SHOWN:
+ sendUdfpsOverlayShown();
default:
Slog.w(TAG, "Unknown message: " + msg.what);
@@ -1489,7 +1515,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onPointerDown(sensorId);
+ mEnrollmentCallback.onUdfpsPointerDown(sensorId);
}
}
@@ -1500,7 +1526,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
mAuthenticationCallback.onUdfpsPointerUp(sensorId);
}
if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onPointerUp(sensorId);
+ mEnrollmentCallback.onUdfpsPointerUp(sensorId);
}
}
@@ -1512,6 +1538,12 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
}
+ private void sendUdfpsOverlayShown() {
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onUdfpsOverlayShown();
+ }
+ }
+
/**
* @hide
*/
@@ -1787,6 +1819,11 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
public void onUdfpsPointerUp(int sensorId) {
mHandler.obtainMessage(MSG_UDFPS_POINTER_UP, sensorId, 0).sendToTarget();
}
+
+ @Override
+ public void onUdfpsOverlayShown() {
+ mHandler.obtainMessage(MSG_UDFPS_OVERLAY_SHOWN).sendToTarget();
+ }
};
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintServiceReceiver.java b/core/java/android/hardware/fingerprint/FingerprintServiceReceiver.java
index a9779b51321b..89d710d4adfe 100644
--- a/core/java/android/hardware/fingerprint/FingerprintServiceReceiver.java
+++ b/core/java/android/hardware/fingerprint/FingerprintServiceReceiver.java
@@ -75,4 +75,9 @@ public class FingerprintServiceReceiver extends IFingerprintServiceReceiver.Stub
public void onUdfpsPointerUp(int sensorId) throws RemoteException {
}
+
+ @Override
+ public void onUdfpsOverlayShown() throws RemoteException {
+
+ }
}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index ec5749ed4f05..ff2f313ac3df 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -193,7 +193,7 @@ interface IFingerprintService {
// Notifies about the fingerprint UI being ready (e.g. HBM illumination is enabled).
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
- void onUiReady(long requestId, int sensorId);
+ void onUdfpsUiEvent(int event, long requestId, int sensorId);
// Sets the controller for managing the UDFPS overlay.
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
index 9cea1fed629d..91a32d78314c 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
@@ -32,4 +32,5 @@ oneway interface IFingerprintServiceReceiver {
void onChallengeGenerated(int sensorId, int userId, long challenge);
void onUdfpsPointerDown(int sensorId);
void onUdfpsPointerUp(int sensorId);
+ void onUdfpsOverlayShown();
}
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index af09a0662795..5b24dcacbf53 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -1294,32 +1294,30 @@ public final class FileUtils {
* Round the given size of a storage device to a nice round power-of-two
* value, such as 256MB or 32GB. This avoids showing weird values like
* "29.5GB" in UI.
- *
- * Some storage devices are still using GiB (powers of 1024) over
- * GB (powers of 1000) measurements and this method takes it into account.
- *
* Round ranges:
* ...
- * [256 GiB + 1; 512 GiB] -> 512 GB
- * [512 GiB + 1; 1 TiB] -> 1 TB
- * [1 TiB + 1; 2 TiB] -> 2 TB
+ * (128 GB; 256 GB] -> 256 GB
+ * (256 GB; 512 GB] -> 512 GB
+ * (512 GB; 1000 GB] -> 1000 GB
+ * (1000 GB; 2000 GB] -> 2000 GB
+ * ...
* etc
*
* @hide
*/
public static long roundStorageSize(long size) {
long val = 1;
- long kiloPow = 1;
- long kibiPow = 1;
- while ((val * kibiPow) < size) {
+ long pow = 1;
+ while ((val * pow) < size) {
val <<= 1;
if (val > 512) {
val = 1;
- kibiPow *= 1024;
- kiloPow *= 1000;
+ pow *= 1000;
}
}
- return val * kiloPow;
+
+ Log.d(TAG, String.format("Rounded bytes from %d to %d", size, val * pow));
+ return val * pow;
}
private static long toBytes(long value, String unit) {
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index bc52744078ea..369a1932e437 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -174,4 +174,5 @@ interface IStorageManager {
boolean isAppIoBlocked(in String volumeUuid, int uid, int tid, int reason) = 95;
void setCloudMediaProvider(in String authority) = 96;
String getCloudMediaProvider() = 97;
+ long getInternalStorageBlockDeviceSize() = 98;
} \ No newline at end of file
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 80dd48825ba7..ee387e7c284f 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1359,6 +1359,15 @@ public class StorageManager {
}
/** {@hide} */
+ public long getInternalStorageBlockDeviceSize() {
+ try {
+ return mStorageManager.getInternalStorageBlockDeviceSize();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
public void mkdirs(File file) {
BlockGuard.getVmPolicy().onPathAccess(file.getAbsolutePath());
try {
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index ac6b2b23cdf5..5d6dfc760b02 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -63,6 +63,7 @@ import java.io.InputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
+import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -1956,9 +1957,8 @@ public class CallLog {
userManager.isUserUnlocked(user) ? CONTENT_URI : SHADOW_CONTENT_URI,
user.getIdentifier());
- if (VERBOSE_LOG) {
- Log.v(LOG_TAG, String.format("Inserting to %s", uri));
- }
+ Log.i(LOG_TAG, String.format(Locale.getDefault(),
+ "addEntryAndRemoveExpiredEntries: provider uri=%s", uri));
try {
// When cleaning up the call log, try to delete older call long entries on a per
@@ -1985,13 +1985,14 @@ public class CallLog {
Log.w(LOG_TAG, "Failed to insert into call log; null result uri.");
}
+ int numDeleted;
if (values.containsKey(PHONE_ACCOUNT_ID)
&& !TextUtils.isEmpty(values.getAsString(PHONE_ACCOUNT_ID))
&& values.containsKey(PHONE_ACCOUNT_COMPONENT_NAME)
&& !TextUtils.isEmpty(values.getAsString(PHONE_ACCOUNT_COMPONENT_NAME))) {
// Only purge entries for the same phone account.
- resolver.delete(uri, "_id IN " +
- "(SELECT _id FROM calls"
+ numDeleted = resolver.delete(uri, "_id IN "
+ + "(SELECT _id FROM calls"
+ " WHERE " + PHONE_ACCOUNT_COMPONENT_NAME + " = ?"
+ " AND " + PHONE_ACCOUNT_ID + " = ?"
+ " ORDER BY " + DEFAULT_SORT_ORDER
@@ -2001,14 +2002,15 @@ public class CallLog {
});
} else {
// No valid phone account specified, so default to the old behavior.
- resolver.delete(uri, "_id IN " +
- "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER
+ numDeleted = resolver.delete(uri, "_id IN "
+ + "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER
+ " LIMIT -1 OFFSET 500)", null);
}
+ Log.i(LOG_TAG, "addEntry: cleaned up " + numDeleted + " old entries");
return result;
} catch (IllegalArgumentException e) {
- Log.w(LOG_TAG, "Failed to insert calllog", e);
+ Log.e(LOG_TAG, "Failed to insert calllog", e);
// Even though we make sure the target user is running and decrypted before calling
// this method, there's a chance that the user just got shut down, in which case
// we'll still get "IllegalArgumentException: Unknown URL content://call_log/calls".
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 11d5ab353232..7af196513cae 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -678,7 +678,7 @@ public class ZygoteInit {
Zygote.applyInvokeWithSystemProperty(parsedArgs);
if (Zygote.nativeSupportsMemoryTagging()) {
- String mode = SystemProperties.get("arm64.memtag.process.system_server", "");
+ String mode = SystemProperties.get("persist.arm64.memtag.system_server", "");
if (mode.isEmpty()) {
/* The system server has ASYNC MTE by default, in order to allow
* system services to specify their own MTE level later, as you
diff --git a/core/res/res/layout/shutdown_dialog.xml b/core/res/res/layout/shutdown_dialog.xml
index ec67aa86bcc9..726c25540e6f 100644
--- a/core/res/res/layout/shutdown_dialog.xml
+++ b/core/res/res/layout/shutdown_dialog.xml
@@ -40,7 +40,7 @@
android:fontFamily="@string/config_headlineFontFamily"/>
<TextView
- android:id="@+id/text2"
+ android:id="@id/text2"
android:layout_width="wrap_content"
android:layout_height="32sp"
android:text="@string/shutdown_progress"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 41783efe5511..498a9328db28 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5193,6 +5193,7 @@
<item>1,1,1.0,0,1</item>
<item>1,1,1.0,.4,1</item>
<item>1,1,1.0,.15,15</item>
+ <item>0,0,0.7,0,1</item>
</string-array>
<!-- The integer index of the selected option in config_udfps_touch_detection_options -->
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index fd7418542a2b..4ae54a052859 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -168,8 +168,9 @@
<bool name="ignore_emergency_number_routing_from_db">false</bool>
<java-symbol type="bool" name="ignore_emergency_number_routing_from_db" />
- <!-- Whether "Virtual DSDA", i.e. in-call IMS connectivity can be provided on both subs with
- only single logical modem, by using its data connection in addition to cellular IMS. -->
- <bool name="config_enable_virtual_dsda">false</bool>
- <java-symbol type="bool" name="config_enable_virtual_dsda" />
+ <!-- Boolean indicating whether allow sending null to modem to clear the previous initial attach
+ data profile -->
+ <bool name="allow_clear_initial_attach_data_profile">false</bool>
+ <java-symbol type="bool" name="allow_clear_initial_attach_data_profile" />
+
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 8762e0c7a66e..cdba1275a6ac 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1786,6 +1786,10 @@
<string name="biometric_dialog_default_title">Verify it\u2019s you</string>
<!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face). [CHAR LIMIT=70] -->
<string name="biometric_dialog_default_subtitle">Use your biometric to continue</string>
+ <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with fingerprint. [CHAR LIMIT=70] -->
+ <string name="biometric_dialog_fingerprint_subtitle">Use your fingerprint to continue</string>
+ <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with face. [CHAR LIMIT=70] -->
+ <string name="biometric_dialog_face_subtitle">Use your face to continue</string>
<!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face) or their screen lock credential (i.e. PIN, pattern, or password). [CHAR LIMIT=90] -->
<string name="biometric_or_screen_lock_dialog_default_subtitle">Use your biometric or screen lock to continue</string>
@@ -1795,6 +1799,8 @@
<string name="biometric_error_user_canceled">Authentication canceled</string>
<!-- Message shown by the biometric dialog when biometric is not recognized -->
<string name="biometric_not_recognized">Not recognized</string>
+ <!-- Message shown by the biometric dialog when face is not recognized [CHAR LIMIT=50] -->
+ <string name="biometric_face_not_recognized">Face not recognized</string>
<!-- Message shown when biometric authentication has been canceled [CHAR LIMIT=50] -->
<string name="biometric_error_canceled">Authentication canceled</string>
<!-- Message returned to applications if BiometricPrompt setAllowDeviceCredentials is enabled but no pin, pattern, or password is set. [CHAR LIMIT=NONE] -->
@@ -1835,6 +1841,8 @@
<string name="fingerprint_error_not_match">Fingerprint not recognized</string>
<!-- Message shown when UDFPS fails to match -->
<string name="fingerprint_udfps_error_not_match">Fingerprint not recognized</string>
+ <!-- Message shown to inform the user a face cannot be recognized and fingerprint should instead be used.[CHAR LIMIT=50] -->
+ <string name="fingerprint_dialog_use_fingerprint_instead">Can\u2019t recognize face. Use fingerprint instead.</string>
<!-- Accessibility message announced when a fingerprint has been authenticated [CHAR LIMIT=NONE] -->
<string name="fingerprint_authenticated">Fingerprint authenticated</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d1c1832da712..ca09d1644d0e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2571,10 +2571,13 @@
<java-symbol type="string" name="biometric_or_screen_lock_app_setting_name" />
<java-symbol type="string" name="biometric_dialog_default_title" />
<java-symbol type="string" name="biometric_dialog_default_subtitle" />
+ <java-symbol type="string" name="biometric_dialog_face_subtitle" />
+ <java-symbol type="string" name="biometric_dialog_fingerprint_subtitle" />
<java-symbol type="string" name="biometric_or_screen_lock_dialog_default_subtitle" />
<java-symbol type="string" name="biometric_error_hw_unavailable" />
<java-symbol type="string" name="biometric_error_user_canceled" />
<java-symbol type="string" name="biometric_not_recognized" />
+ <java-symbol type="string" name="biometric_face_not_recognized" />
<java-symbol type="string" name="biometric_error_canceled" />
<java-symbol type="string" name="biometric_error_device_not_secured" />
<java-symbol type="string" name="biometric_error_generic" />
@@ -2592,6 +2595,7 @@
<java-symbol type="string" name="fingerprint_error_vendor_unknown" />
<java-symbol type="string" name="fingerprint_error_not_match" />
<java-symbol type="string" name="fingerprint_udfps_error_not_match" />
+ <java-symbol type="string" name="fingerprint_dialog_use_fingerprint_instead" />
<java-symbol type="string" name="fingerprint_acquired_partial" />
<java-symbol type="string" name="fingerprint_acquired_insufficient" />
<java-symbol type="string" name="fingerprint_acquired_imager_dirty" />
@@ -5025,6 +5029,7 @@
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" />
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" />
+
<java-symbol name="materialColorOnSecondaryFixedVariant" type="attr"/>
<java-symbol name="materialColorOnTertiaryFixedVariant" type="attr"/>
<java-symbol name="materialColorSurfaceContainerLowest" type="attr"/>
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
index 394ff0ae9a2e..a0d8183b8da7 100644
--- a/core/tests/coretests/src/android/os/FileUtilsTest.java
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -505,45 +505,32 @@ public class FileUtilsTest {
@Test
public void testRoundStorageSize() throws Exception {
- final long GB1 = DataUnit.GIGABYTES.toBytes(1);
- final long GiB1 = DataUnit.GIBIBYTES.toBytes(1);
- final long GB2 = DataUnit.GIGABYTES.toBytes(2);
- final long GiB2 = DataUnit.GIBIBYTES.toBytes(2);
- final long GiB128 = DataUnit.GIBIBYTES.toBytes(128);
- final long GB256 = DataUnit.GIGABYTES.toBytes(256);
- final long GiB256 = DataUnit.GIBIBYTES.toBytes(256);
- final long GB512 = DataUnit.GIGABYTES.toBytes(512);
- final long GiB512 = DataUnit.GIBIBYTES.toBytes(512);
- final long TB1 = DataUnit.TERABYTES.toBytes(1);
- final long TiB1 = DataUnit.TEBIBYTES.toBytes(1);
- final long TB2 = DataUnit.TERABYTES.toBytes(2);
- final long TiB2 = DataUnit.TEBIBYTES.toBytes(2);
- final long TB4 = DataUnit.TERABYTES.toBytes(4);
- final long TiB4 = DataUnit.TEBIBYTES.toBytes(4);
- final long TB8 = DataUnit.TERABYTES.toBytes(8);
- final long TiB8 = DataUnit.TEBIBYTES.toBytes(8);
-
- assertEquals(GB1, roundStorageSize(GB1 - 1));
- assertEquals(GB1, roundStorageSize(GB1));
- assertEquals(GB1, roundStorageSize(GB1 + 1));
- assertEquals(GB1, roundStorageSize(GiB1 - 1));
- assertEquals(GB1, roundStorageSize(GiB1));
- assertEquals(GB2, roundStorageSize(GiB1 + 1));
- assertEquals(GB2, roundStorageSize(GiB2));
-
- assertEquals(GB256, roundStorageSize(GiB128 + 1));
- assertEquals(GB256, roundStorageSize(GiB256));
- assertEquals(GB512, roundStorageSize(GiB256 + 1));
- assertEquals(GB512, roundStorageSize(GiB512));
- assertEquals(TB1, roundStorageSize(GiB512 + 1));
- assertEquals(TB1, roundStorageSize(TiB1));
- assertEquals(TB2, roundStorageSize(TiB1 + 1));
- assertEquals(TB2, roundStorageSize(TiB2));
- assertEquals(TB4, roundStorageSize(TiB2 + 1));
- assertEquals(TB4, roundStorageSize(TiB4));
- assertEquals(TB8, roundStorageSize(TiB4 + 1));
- assertEquals(TB8, roundStorageSize(TiB8));
- assertEquals(TB1, roundStorageSize(1013077688320L)); // b/268571529
+ final long M256 = DataUnit.MEGABYTES.toBytes(256);
+ final long M512 = DataUnit.MEGABYTES.toBytes(512);
+ final long G1 = DataUnit.GIGABYTES.toBytes(1);
+ final long G2 = DataUnit.GIGABYTES.toBytes(2);
+ final long G32 = DataUnit.GIGABYTES.toBytes(32);
+ final long G64 = DataUnit.GIGABYTES.toBytes(64);
+ final long G512 = DataUnit.GIGABYTES.toBytes(512);
+ final long G1000 = DataUnit.TERABYTES.toBytes(1);
+ final long G2000 = DataUnit.TERABYTES.toBytes(2);
+
+ assertEquals(M256, roundStorageSize(M256 - 1));
+ assertEquals(M256, roundStorageSize(M256));
+ assertEquals(M512, roundStorageSize(M256 + 1));
+ assertEquals(M512, roundStorageSize(M512 - 1));
+ assertEquals(M512, roundStorageSize(M512));
+ assertEquals(G1, roundStorageSize(M512 + 1));
+ assertEquals(G1, roundStorageSize(G1));
+ assertEquals(G2, roundStorageSize(G1 + 1));
+
+ assertEquals(G32, roundStorageSize(G32 - 1));
+ assertEquals(G32, roundStorageSize(G32));
+ assertEquals(G64, roundStorageSize(G32 + 1));
+
+ assertEquals(G512, roundStorageSize(G512 - 1));
+ assertEquals(G1000, roundStorageSize(G512 + 1));
+ assertEquals(G2000, roundStorageSize(G1000 + 1));
}
@Test
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 65727b6145e4..bbd17cd67dc7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -795,6 +795,14 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
private void onDisplayChangedUncheck(DisplayLayout layout, boolean saveRestoreSnapFraction) {
+ if (mPipTransitionState.getInSwipePipToHomeTransition()) {
+ // If orientation is changed when performing swipe-pip animation, DisplayLayout has
+ // been updated in startSwipePipToHome. So it is unnecessary to update again when
+ // receiving onDisplayConfigurationChanged. This also avoids TouchHandler.userResizeTo
+ // update surface position in different orientation by the intermediate state. The
+ // desired resize will be done by the end of transition.
+ return;
+ }
Runnable updateDisplayLayout = () -> {
final boolean fromRotation = Transitions.ENABLE_SHELL_TRANSITIONS
&& mPipDisplayLayoutState.getDisplayLayout().rotation() != layout.rotation();
diff --git a/packages/SettingsLib/IllustrationPreference/res/values/colors.xml b/packages/SettingsLib/IllustrationPreference/res/values/colors.xml
index 5d6c343c1fbc..accaa67db1fc 100644
--- a/packages/SettingsLib/IllustrationPreference/res/values/colors.xml
+++ b/packages/SettingsLib/IllustrationPreference/res/values/colors.xml
@@ -25,10 +25,12 @@
<color name="settingslib_color_blue100">#d2e3fc</color>
<color name="settingslib_color_blue50">#e8f0fe</color>
<color name="settingslib_color_green600">#1e8e3e</color>
+ <color name="settingslib_color_green500">#34A853</color>
<color name="settingslib_color_green400">#5bb974</color>
<color name="settingslib_color_green100">#ceead6</color>
<color name="settingslib_color_green50">#e6f4ea</color>
<color name="settingslib_color_red600">#d93025</color>
+ <color name="settingslib_color_red500">#B3261E</color>
<color name="settingslib_color_red400">#ee675c</color>
<color name="settingslib_color_red100">#fad2cf</color>
<color name="settingslib_color_red50">#fce8e6</color>
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
index 5e2c43735361..f166a18f528d 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
@@ -42,9 +42,6 @@ public class LottieColorUtils {
".grey600",
R.color.settingslib_color_grey400);
map.put(
- ".grey700",
- R.color.settingslib_color_grey500);
- map.put(
".grey800",
R.color.settingslib_color_grey300);
map.put(
@@ -62,6 +59,12 @@ public class LottieColorUtils {
map.put(
".green400",
R.color.settingslib_color_green600);
+ map.put(
+ ".green200",
+ R.color.settingslib_color_green500);
+ map.put(
+ ".red200",
+ R.color.settingslib_color_red500);
DARK_TO_LIGHT_THEME_COLOR_MAP = Collections.unmodifiableMap(map);
}
diff --git a/packages/SettingsLib/Spa/tests/Android.bp b/packages/SettingsLib/Spa/tests/Android.bp
index b4c67ccda6f2..f9e64aee1513 100644
--- a/packages/SettingsLib/Spa/tests/Android.bp
+++ b/packages/SettingsLib/Spa/tests/Android.bp
@@ -31,7 +31,6 @@ android_test {
"SpaLib",
"SpaLibTestUtils",
"androidx.compose.runtime_runtime",
- "androidx.lifecycle_lifecycle-runtime-testing",
"androidx.test.ext.junit",
"androidx.test.runner",
"mockito-target-minus-junit4",
diff --git a/packages/SettingsLib/Spa/testutils/Android.bp b/packages/SettingsLib/Spa/testutils/Android.bp
index 2c1e1c2abc2c..e4d56cc4f2a0 100644
--- a/packages/SettingsLib/Spa/testutils/Android.bp
+++ b/packages/SettingsLib/Spa/testutils/Android.bp
@@ -29,6 +29,7 @@ android_library {
"androidx.compose.runtime_runtime",
"androidx.compose.ui_ui-test-junit4",
"androidx.compose.ui_ui-test-manifest",
+ "androidx.lifecycle_lifecycle-runtime-testing",
"mockito",
"truth-prebuilt",
],
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicSummary.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicSummary.java
index c9d9b57c7170..5b7899b1e3af 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicSummary.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicSummary.java
@@ -16,7 +16,7 @@
package com.android.settingslib.drawer;
-/** Interface for {@link SwitchController} whose instances support dynamic summary */
+/** Interface for {@link EntryController} whose instances support dynamic summary */
public interface DynamicSummary {
/** @return the dynamic summary text */
String getDynamicSummary();
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicTitle.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicTitle.java
index af711ddd59c2..cb157734aa6a 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicTitle.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicTitle.java
@@ -16,7 +16,7 @@
package com.android.settingslib.drawer;
-/** Interface for {@link SwitchController} whose instances support dynamic title */
+/** Interface for {@link EntryController} whose instances support dynamic title */
public interface DynamicTitle {
/** @return the dynamic title text */
String getDynamicTitle();
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntriesProvider.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntriesProvider.java
new file mode 100644
index 000000000000..1c14c0a72ce4
--- /dev/null
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntriesProvider.java
@@ -0,0 +1,222 @@
+/*
+ * 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.settingslib.drawer;
+
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An abstract class for injecting entries to Settings.
+ */
+public abstract class EntriesProvider extends ContentProvider {
+ private static final String TAG = "EntriesProvider";
+
+ public static final String METHOD_GET_ENTRY_DATA = "getEntryData";
+ public static final String METHOD_GET_PROVIDER_ICON = "getProviderIcon";
+ public static final String METHOD_GET_DYNAMIC_TITLE = "getDynamicTitle";
+ public static final String METHOD_GET_DYNAMIC_SUMMARY = "getDynamicSummary";
+ public static final String METHOD_IS_CHECKED = "isChecked";
+ public static final String METHOD_ON_CHECKED_CHANGED = "onCheckedChanged";
+
+ /**
+ * @deprecated use {@link #METHOD_GET_ENTRY_DATA} instead.
+ */
+ @Deprecated
+ public static final String METHOD_GET_SWITCH_DATA = "getSwitchData";
+
+ public static final String EXTRA_ENTRY_DATA = "entry_data";
+ public static final String EXTRA_SWITCH_CHECKED_STATE = "checked_state";
+ public static final String EXTRA_SWITCH_SET_CHECKED_ERROR = "set_checked_error";
+ public static final String EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE = "set_checked_error_message";
+
+ /**
+ * @deprecated use {@link #EXTRA_ENTRY_DATA} instead.
+ */
+ @Deprecated
+ public static final String EXTRA_SWITCH_DATA = "switch_data";
+
+ private String mAuthority;
+ private final Map<String, EntryController> mControllerMap = new LinkedHashMap<>();
+ private final List<Bundle> mEntryDataList = new ArrayList<>();
+
+ /**
+ * Get a list of {@link EntryController} for this provider.
+ */
+ protected abstract List<? extends EntryController> createEntryControllers();
+
+ protected EntryController getController(String key) {
+ return mControllerMap.get(key);
+ }
+
+ @Override
+ public void attachInfo(Context context, ProviderInfo info) {
+ mAuthority = info.authority;
+ Log.i(TAG, mAuthority);
+ super.attachInfo(context, info);
+ }
+
+ @Override
+ public boolean onCreate() {
+ final List<? extends EntryController> controllers = createEntryControllers();
+ if (controllers == null || controllers.isEmpty()) {
+ throw new IllegalArgumentException();
+ }
+
+ for (EntryController controller : controllers) {
+ final String key = controller.getKey();
+ if (TextUtils.isEmpty(key)) {
+ throw new NullPointerException("Entry key cannot be null: "
+ + controller.getClass().getSimpleName());
+ } else if (mControllerMap.containsKey(key)) {
+ throw new IllegalArgumentException("Entry key " + key + " is duplicated by: "
+ + controller.getClass().getSimpleName());
+ }
+
+ controller.setAuthority(mAuthority);
+ mControllerMap.put(key, controller);
+ if (!(controller instanceof PrimarySwitchController)) {
+ mEntryDataList.add(controller.getBundle());
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public Bundle call(String method, String uriString, Bundle extras) {
+ final Bundle bundle = new Bundle();
+ final String key = extras != null
+ ? extras.getString(META_DATA_PREFERENCE_KEYHINT)
+ : null;
+ if (TextUtils.isEmpty(key)) {
+ switch (method) {
+ case METHOD_GET_ENTRY_DATA:
+ bundle.putParcelableList(EXTRA_ENTRY_DATA, mEntryDataList);
+ return bundle;
+ case METHOD_GET_SWITCH_DATA:
+ bundle.putParcelableList(EXTRA_SWITCH_DATA, mEntryDataList);
+ return bundle;
+ default:
+ return null;
+ }
+ }
+
+ final EntryController controller = mControllerMap.get(key);
+ if (controller == null) {
+ return null;
+ }
+
+ switch (method) {
+ case METHOD_GET_ENTRY_DATA:
+ case METHOD_GET_SWITCH_DATA:
+ if (!(controller instanceof PrimarySwitchController)) {
+ return controller.getBundle();
+ }
+ break;
+ case METHOD_GET_PROVIDER_ICON:
+ if (controller instanceof ProviderIcon) {
+ return ((ProviderIcon) controller).getProviderIcon();
+ }
+ break;
+ case METHOD_GET_DYNAMIC_TITLE:
+ if (controller instanceof DynamicTitle) {
+ bundle.putString(META_DATA_PREFERENCE_TITLE,
+ ((DynamicTitle) controller).getDynamicTitle());
+ return bundle;
+ }
+ break;
+ case METHOD_GET_DYNAMIC_SUMMARY:
+ if (controller instanceof DynamicSummary) {
+ bundle.putString(META_DATA_PREFERENCE_SUMMARY,
+ ((DynamicSummary) controller).getDynamicSummary());
+ return bundle;
+ }
+ break;
+ case METHOD_IS_CHECKED:
+ if (controller instanceof ProviderSwitch) {
+ bundle.putBoolean(EXTRA_SWITCH_CHECKED_STATE,
+ ((ProviderSwitch) controller).isSwitchChecked());
+ return bundle;
+ }
+ break;
+ case METHOD_ON_CHECKED_CHANGED:
+ if (controller instanceof ProviderSwitch) {
+ return onSwitchCheckedChanged(extras.getBoolean(EXTRA_SWITCH_CHECKED_STATE),
+ (ProviderSwitch) controller);
+ }
+ break;
+ }
+ return null;
+ }
+
+ private Bundle onSwitchCheckedChanged(boolean checked, ProviderSwitch controller) {
+ final boolean success = controller.onSwitchCheckedChanged(checked);
+ final Bundle bundle = new Bundle();
+ bundle.putBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR, !success);
+ if (success) {
+ if (controller instanceof DynamicSummary) {
+ ((EntryController) controller).notifySummaryChanged(getContext());
+ }
+ } else {
+ bundle.putString(EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE,
+ controller.getSwitchErrorMessage(checked));
+ }
+ return bundle;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+}
+
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntryController.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntryController.java
new file mode 100644
index 000000000000..5d6e6a3adeb7
--- /dev/null
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntryController.java
@@ -0,0 +1,246 @@
+/*
+ * 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.settingslib.drawer;
+
+import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_SUMMARY;
+import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_TITLE;
+import static com.android.settingslib.drawer.TileUtils.EXTRA_CATEGORY_KEY;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_TINTABLE;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_PENDING_INTENT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE_URI;
+
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+
+/**
+ * A controller that manages events for switch.
+ */
+public abstract class EntryController {
+
+ private String mAuthority;
+
+ /**
+ * Returns the key for this switch.
+ */
+ public abstract String getKey();
+
+ /**
+ * Returns the {@link MetaData} for this switch.
+ */
+ protected abstract MetaData getMetaData();
+
+ /**
+ * Notify registered observers that title was updated and attempt to sync changes.
+ */
+ public void notifyTitleChanged(Context context) {
+ if (this instanceof DynamicTitle) {
+ notifyChanged(context, METHOD_GET_DYNAMIC_TITLE);
+ }
+ }
+
+ /**
+ * Notify registered observers that summary was updated and attempt to sync changes.
+ */
+ public void notifySummaryChanged(Context context) {
+ if (this instanceof DynamicSummary) {
+ notifyChanged(context, METHOD_GET_DYNAMIC_SUMMARY);
+ }
+ }
+
+ void setAuthority(String authority) {
+ mAuthority = authority;
+ }
+
+ Bundle getBundle() {
+ final MetaData metaData = getMetaData();
+ if (metaData == null) {
+ throw new NullPointerException("Should not return null in getMetaData()");
+ }
+
+ final Bundle bundle = metaData.build();
+ final String uriString = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(mAuthority)
+ .build()
+ .toString();
+ bundle.putString(META_DATA_PREFERENCE_KEYHINT, getKey());
+ if (this instanceof ProviderIcon) {
+ bundle.putString(META_DATA_PREFERENCE_ICON_URI, uriString);
+ }
+ if (this instanceof DynamicTitle) {
+ bundle.putString(META_DATA_PREFERENCE_TITLE_URI, uriString);
+ }
+ if (this instanceof DynamicSummary) {
+ bundle.putString(META_DATA_PREFERENCE_SUMMARY_URI, uriString);
+ }
+ if (this instanceof ProviderSwitch) {
+ bundle.putString(META_DATA_PREFERENCE_SWITCH_URI, uriString);
+ }
+ return bundle;
+ }
+
+ private void notifyChanged(Context context, String method) {
+ final Uri uri = TileUtils.buildUri(mAuthority, method, getKey());
+ context.getContentResolver().notifyChange(uri, null);
+ }
+
+ /**
+ * Collects all meta data of the item.
+ */
+ protected static class MetaData {
+ private String mCategory;
+ private int mOrder;
+ @DrawableRes
+ private int mIcon;
+ private int mIconBackgroundHint;
+ private int mIconBackgroundArgb;
+ private Boolean mIconTintable;
+ @StringRes
+ private int mTitleId;
+ private String mTitle;
+ @StringRes
+ private int mSummaryId;
+ private String mSummary;
+ private PendingIntent mPendingIntent;
+
+ /**
+ * @param category the category of the switch. This value must be from {@link CategoryKey}.
+ */
+ public MetaData(@NonNull String category) {
+ mCategory = category;
+ }
+
+ /**
+ * Set the order of the item that should be displayed on screen. Bigger value items displays
+ * closer on top.
+ */
+ public MetaData setOrder(int order) {
+ mOrder = order;
+ return this;
+ }
+
+ /** Set the icon that should be displayed for the item. */
+ public MetaData setIcon(@DrawableRes int icon) {
+ mIcon = icon;
+ return this;
+ }
+
+ /** Set the icon background color. The value may or may not be used by Settings app. */
+ public MetaData setIconBackgoundHint(int hint) {
+ mIconBackgroundHint = hint;
+ return this;
+ }
+
+ /** Set the icon background color as raw ARGB. */
+ public MetaData setIconBackgoundArgb(int argb) {
+ mIconBackgroundArgb = argb;
+ return this;
+ }
+
+ /** Specify whether the icon is tintable. */
+ public MetaData setIconTintable(boolean tintable) {
+ mIconTintable = tintable;
+ return this;
+ }
+
+ /** Set the title that should be displayed for the item. */
+ public MetaData setTitle(@StringRes int id) {
+ mTitleId = id;
+ return this;
+ }
+
+ /** Set the title that should be displayed for the item. */
+ public MetaData setTitle(String title) {
+ mTitle = title;
+ return this;
+ }
+
+ /** Set the summary text that should be displayed for the item. */
+ public MetaData setSummary(@StringRes int id) {
+ mSummaryId = id;
+ return this;
+ }
+
+ /** Set the summary text that should be displayed for the item. */
+ public MetaData setSummary(String summary) {
+ mSummary = summary;
+ return this;
+ }
+
+ public MetaData setPendingIntent(PendingIntent pendingIntent) {
+ mPendingIntent = pendingIntent;
+ return this;
+ }
+
+ protected Bundle build() {
+ final Bundle bundle = new Bundle();
+ bundle.putString(EXTRA_CATEGORY_KEY, mCategory);
+
+ if (mOrder != 0) {
+ bundle.putInt(META_DATA_KEY_ORDER, mOrder);
+ }
+
+ if (mIcon != 0) {
+ bundle.putInt(META_DATA_PREFERENCE_ICON, mIcon);
+ }
+ if (mIconBackgroundHint != 0) {
+ bundle.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, mIconBackgroundHint);
+ }
+ if (mIconBackgroundArgb != 0) {
+ bundle.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB, mIconBackgroundArgb);
+ }
+ if (mIconTintable != null) {
+ bundle.putBoolean(META_DATA_PREFERENCE_ICON_TINTABLE, mIconTintable);
+ }
+
+ if (mTitleId != 0) {
+ bundle.putInt(META_DATA_PREFERENCE_TITLE, mTitleId);
+ } else if (mTitle != null) {
+ bundle.putString(META_DATA_PREFERENCE_TITLE, mTitle);
+ }
+
+ if (mSummaryId != 0) {
+ bundle.putInt(META_DATA_PREFERENCE_SUMMARY, mSummaryId);
+ } else if (mSummary != null) {
+ bundle.putString(META_DATA_PREFERENCE_SUMMARY, mSummary);
+ }
+
+ if (mPendingIntent != null) {
+ bundle.putParcelable(META_DATA_PREFERENCE_PENDING_INTENT, mPendingIntent);
+ }
+
+ return bundle;
+ }
+ }
+}
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderIcon.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderIcon.java
index 2945d5c1099a..3aa6fcb06016 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderIcon.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderIcon.java
@@ -19,7 +19,7 @@ package com.android.settingslib.drawer;
import android.os.Bundle;
/**
- * Interface for {@link SwitchController} whose instances support icon provided from the content
+ * Interface for {@link EntryController} whose instances support icon provided from the content
* provider
*/
public interface ProviderIcon {
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderSwitch.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderSwitch.java
new file mode 100644
index 000000000000..47eb31cefa29
--- /dev/null
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderSwitch.java
@@ -0,0 +1,41 @@
+/*
+ * 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.settingslib.drawer;
+
+/**
+ * Interface for {@link EntryController} whose instances support switch widget provided from the
+ * content provider
+ */
+public interface ProviderSwitch {
+ /**
+ * Returns the checked state of this switch.
+ */
+ boolean isSwitchChecked();
+
+ /**
+ * Called when the checked state of this switch is changed.
+ *
+ * @return true if the checked state was successfully changed, otherwise false
+ */
+ boolean onSwitchCheckedChanged(boolean checked);
+
+ /**
+ * Returns the error message which will be toasted when {@link #onSwitchCheckedChanged} returns
+ * false.
+ */
+ String getSwitchErrorMessage(boolean attemptedChecked);
+}
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderTile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderTile.java
index 54da585aba7a..b775e93bf69c 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderTile.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderTile.java
@@ -75,7 +75,7 @@ public class ProviderTile extends Tile {
if (infoList != null && !infoList.isEmpty()) {
final ProviderInfo providerInfo = infoList.get(0).providerInfo;
mComponentInfo = providerInfo;
- setMetaData(TileUtils.getSwitchDataFromProvider(context, providerInfo.authority,
+ setMetaData(TileUtils.getEntryDataFromProvider(context, providerInfo.authority,
mKey));
} else {
Log.e(TAG, "Cannot find package info for "
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchController.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchController.java
index 23669b2743ce..a1a4e5867299 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchController.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchController.java
@@ -16,38 +16,16 @@
package com.android.settingslib.drawer;
-import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_SUMMARY;
-import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_TITLE;
-import static com.android.settingslib.drawer.SwitchesProvider.METHOD_IS_CHECKED;
-import static com.android.settingslib.drawer.TileUtils.EXTRA_CATEGORY_KEY;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_TINTABLE;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE_URI;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Bundle;
-
-import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
-import androidx.annotation.StringRes;
/**
* A controller that manages events for switch.
+ *
+ * @deprecated Use {@link EntriesProvider} with {@link ProviderSwitch} instead.
*/
-public abstract class SwitchController {
+@Deprecated
+public abstract class SwitchController extends EntryController implements ProviderSwitch {
- private String mAuthority;
/**
* Returns the key for this switch.
@@ -55,11 +33,6 @@ public abstract class SwitchController {
public abstract String getSwitchKey();
/**
- * Returns the {@link MetaData} for this switch.
- */
- protected abstract MetaData getMetaData();
-
- /**
* Returns the checked state of this switch.
*/
protected abstract boolean isChecked();
@@ -76,181 +49,41 @@ public abstract class SwitchController {
*/
protected abstract String getErrorMessage(boolean attemptedChecked);
- /**
- * Notify registered observers that title was updated and attempt to sync changes.
- */
- public void notifyTitleChanged(Context context) {
- if (this instanceof DynamicTitle) {
- notifyChanged(context, METHOD_GET_DYNAMIC_TITLE);
- }
- }
-
- /**
- * Notify registered observers that summary was updated and attempt to sync changes.
- */
- public void notifySummaryChanged(Context context) {
- if (this instanceof DynamicSummary) {
- notifyChanged(context, METHOD_GET_DYNAMIC_SUMMARY);
- }
- }
-
- /**
- * Notify registered observers that checked state was updated and attempt to sync changes.
- */
- public void notifyCheckedChanged(Context context) {
- notifyChanged(context, METHOD_IS_CHECKED);
+ @Override
+ public String getKey() {
+ return getSwitchKey();
}
- void setAuthority(String authority) {
- mAuthority = authority;
+ @Override
+ public boolean isSwitchChecked() {
+ return isChecked();
}
- Bundle getBundle() {
- final MetaData metaData = getMetaData();
- if (metaData == null) {
- throw new NullPointerException("Should not return null in getMetaData()");
- }
-
- final Bundle bundle = metaData.build();
- final String uriString = new Uri.Builder()
- .scheme(ContentResolver.SCHEME_CONTENT)
- .authority(mAuthority)
- .build()
- .toString();
- bundle.putString(META_DATA_PREFERENCE_KEYHINT, getSwitchKey());
- bundle.putString(META_DATA_PREFERENCE_SWITCH_URI, uriString);
- if (this instanceof ProviderIcon) {
- bundle.putString(META_DATA_PREFERENCE_ICON_URI, uriString);
- }
- if (this instanceof DynamicTitle) {
- bundle.putString(META_DATA_PREFERENCE_TITLE_URI, uriString);
- }
- if (this instanceof DynamicSummary) {
- bundle.putString(META_DATA_PREFERENCE_SUMMARY_URI, uriString);
- }
- return bundle;
+ @Override
+ public boolean onSwitchCheckedChanged(boolean checked) {
+ return onCheckedChanged(checked);
}
- private void notifyChanged(Context context, String method) {
- final Uri uri = TileUtils.buildUri(mAuthority, method, getSwitchKey());
- context.getContentResolver().notifyChange(uri, null);
+ @Override
+ public String getSwitchErrorMessage(boolean attemptedChecked) {
+ return getErrorMessage(attemptedChecked);
}
/**
- * Collects all meta data of the item.
+ * Same as {@link EntryController.MetaData}, for backwards compatibility purpose.
+ *
+ * @deprecated Use {@link EntryController.MetaData} instead.
*/
- protected static class MetaData {
- private String mCategory;
- private int mOrder;
- @DrawableRes
- private int mIcon;
- private int mIconBackgroundHint;
- private int mIconBackgroundArgb;
- private Boolean mIconTintable;
- @StringRes
- private int mTitleId;
- private String mTitle;
- @StringRes
- private int mSummaryId;
- private String mSummary;
-
+ @Deprecated
+ protected static class MetaData extends EntryController.MetaData {
/**
* @param category the category of the switch. This value must be from {@link CategoryKey}.
+ *
+ * @deprecated Use {@link EntryController.MetaData} instead.
*/
+ @Deprecated
public MetaData(@NonNull String category) {
- mCategory = category;
- }
-
- /**
- * Set the order of the item that should be displayed on screen. Bigger value items displays
- * closer on top.
- */
- public MetaData setOrder(int order) {
- mOrder = order;
- return this;
- }
-
- /** Set the icon that should be displayed for the item. */
- public MetaData setIcon(@DrawableRes int icon) {
- mIcon = icon;
- return this;
- }
-
- /** Set the icon background color. The value may or may not be used by Settings app. */
- public MetaData setIconBackgoundHint(int hint) {
- mIconBackgroundHint = hint;
- return this;
- }
-
- /** Set the icon background color as raw ARGB. */
- public MetaData setIconBackgoundArgb(int argb) {
- mIconBackgroundArgb = argb;
- return this;
- }
-
- /** Specify whether the icon is tintable. */
- public MetaData setIconTintable(boolean tintable) {
- mIconTintable = tintable;
- return this;
- }
-
- /** Set the title that should be displayed for the item. */
- public MetaData setTitle(@StringRes int id) {
- mTitleId = id;
- return this;
- }
-
- /** Set the title that should be displayed for the item. */
- public MetaData setTitle(String title) {
- mTitle = title;
- return this;
- }
-
- /** Set the summary text that should be displayed for the item. */
- public MetaData setSummary(@StringRes int id) {
- mSummaryId = id;
- return this;
- }
-
- /** Set the summary text that should be displayed for the item. */
- public MetaData setSummary(String summary) {
- mSummary = summary;
- return this;
- }
-
- private Bundle build() {
- final Bundle bundle = new Bundle();
- bundle.putString(EXTRA_CATEGORY_KEY, mCategory);
-
- if (mOrder != 0) {
- bundle.putInt(META_DATA_KEY_ORDER, mOrder);
- }
-
- if (mIcon != 0) {
- bundle.putInt(META_DATA_PREFERENCE_ICON, mIcon);
- }
- if (mIconBackgroundHint != 0) {
- bundle.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, mIconBackgroundHint);
- }
- if (mIconBackgroundArgb != 0) {
- bundle.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB, mIconBackgroundArgb);
- }
- if (mIconTintable != null) {
- bundle.putBoolean(META_DATA_PREFERENCE_ICON_TINTABLE, mIconTintable);
- }
-
- if (mTitleId != 0) {
- bundle.putInt(META_DATA_PREFERENCE_TITLE, mTitleId);
- } else if (mTitle != null) {
- bundle.putString(META_DATA_PREFERENCE_TITLE, mTitle);
- }
-
- if (mSummaryId != 0) {
- bundle.putInt(META_DATA_PREFERENCE_SUMMARY, mSummaryId);
- } else if (mSummary != null) {
- bundle.putString(META_DATA_PREFERENCE_SUMMARY, mSummary);
- }
- return bundle;
+ super(category);
}
}
}
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java
index f2b3e30dc252..ad00ced8a3ac 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java
@@ -16,46 +16,15 @@
package com.android.settingslib.drawer;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.pm.ProviderInfo;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
/**
* An abstract class for injecting switches to Settings.
+ *
+ * @deprecated Use {@link EntriesProvider} instead.
*/
-public abstract class SwitchesProvider extends ContentProvider {
- private static final String TAG = "SwitchesProvider";
-
- public static final String METHOD_GET_SWITCH_DATA = "getSwitchData";
- public static final String METHOD_GET_PROVIDER_ICON = "getProviderIcon";
- public static final String METHOD_GET_DYNAMIC_TITLE = "getDynamicTitle";
- public static final String METHOD_GET_DYNAMIC_SUMMARY = "getDynamicSummary";
- public static final String METHOD_IS_CHECKED = "isChecked";
- public static final String METHOD_ON_CHECKED_CHANGED = "onCheckedChanged";
-
- public static final String EXTRA_SWITCH_DATA = "switch_data";
- public static final String EXTRA_SWITCH_CHECKED_STATE = "checked_state";
- public static final String EXTRA_SWITCH_SET_CHECKED_ERROR = "set_checked_error";
- public static final String EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE = "set_checked_error_message";
-
- private String mAuthority;
- private final Map<String, SwitchController> mControllerMap = new LinkedHashMap<>();
- private final List<Bundle> mSwitchDataList = new ArrayList<>();
+@Deprecated
+public abstract class SwitchesProvider extends EntriesProvider {
/**
* Get a list of {@link SwitchController} for this provider.
@@ -63,129 +32,7 @@ public abstract class SwitchesProvider extends ContentProvider {
protected abstract List<SwitchController> createSwitchControllers();
@Override
- public void attachInfo(Context context, ProviderInfo info) {
- mAuthority = info.authority;
- Log.i(TAG, mAuthority);
- super.attachInfo(context, info);
- }
-
- @Override
- public boolean onCreate() {
- final List<SwitchController> controllers = createSwitchControllers();
- if (controllers == null || controllers.isEmpty()) {
- throw new IllegalArgumentException();
- }
-
- controllers.forEach(controller -> {
- final String key = controller.getSwitchKey();
- if (TextUtils.isEmpty(key)) {
- throw new NullPointerException("Switch key cannot be null: "
- + controller.getClass().getSimpleName());
- } else if (mControllerMap.containsKey(key)) {
- throw new IllegalArgumentException("Switch key " + key + " is duplicated by: "
- + controller.getClass().getSimpleName());
- }
-
- controller.setAuthority(mAuthority);
- mControllerMap.put(key, controller);
- if (!(controller instanceof PrimarySwitchController)) {
- mSwitchDataList.add(controller.getBundle());
- }
- });
- return true;
- }
-
- @Override
- public Bundle call(String method, String uriString, Bundle extras) {
- final Bundle bundle = new Bundle();
- final String key = extras != null
- ? extras.getString(META_DATA_PREFERENCE_KEYHINT)
- : null;
- if (TextUtils.isEmpty(key)) {
- if (METHOD_GET_SWITCH_DATA.equals(method)) {
- bundle.putParcelableList(EXTRA_SWITCH_DATA, mSwitchDataList);
- return bundle;
- }
- return null;
- }
-
- final SwitchController controller = mControllerMap.get(key);
- if (controller == null) {
- return null;
- }
-
- switch (method) {
- case METHOD_GET_SWITCH_DATA:
- if (!(controller instanceof PrimarySwitchController)) {
- return controller.getBundle();
- }
- break;
- case METHOD_GET_PROVIDER_ICON:
- if (controller instanceof ProviderIcon) {
- return ((ProviderIcon) controller).getProviderIcon();
- }
- break;
- case METHOD_GET_DYNAMIC_TITLE:
- if (controller instanceof DynamicTitle) {
- bundle.putString(META_DATA_PREFERENCE_TITLE,
- ((DynamicTitle) controller).getDynamicTitle());
- return bundle;
- }
- break;
- case METHOD_GET_DYNAMIC_SUMMARY:
- if (controller instanceof DynamicSummary) {
- bundle.putString(META_DATA_PREFERENCE_SUMMARY,
- ((DynamicSummary) controller).getDynamicSummary());
- return bundle;
- }
- break;
- case METHOD_IS_CHECKED:
- bundle.putBoolean(EXTRA_SWITCH_CHECKED_STATE, controller.isChecked());
- return bundle;
- case METHOD_ON_CHECKED_CHANGED:
- return onCheckedChanged(extras.getBoolean(EXTRA_SWITCH_CHECKED_STATE), controller);
- }
- return null;
- }
-
- private Bundle onCheckedChanged(boolean checked, SwitchController controller) {
- final boolean success = controller.onCheckedChanged(checked);
- final Bundle bundle = new Bundle();
- bundle.putBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR, !success);
- if (success) {
- if (controller instanceof DynamicSummary) {
- controller.notifySummaryChanged(getContext());
- }
- } else {
- bundle.putString(EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE,
- controller.getErrorMessage(checked));
- }
- return bundle;
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getType(Uri uri) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException();
+ protected List<? extends EntryController> createEntryControllers() {
+ return createSwitchControllers();
}
}
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
index a0c8ac4e0a51..1a938d6ec37e 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
@@ -19,6 +19,7 @@ package com.android.settingslib.drawer;
import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_PROFILE;
import static com.android.settingslib.drawer.TileUtils.META_DATA_NEW_TASK;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_GROUP_KEY;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
@@ -29,6 +30,7 @@ import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITL
import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
import static com.android.settingslib.drawer.TileUtils.PROFILE_PRIMARY;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ComponentInfo;
@@ -47,6 +49,7 @@ import androidx.annotation.VisibleForTesting;
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.HashMap;
/**
* Description of a single dashboard tile that the user can select.
@@ -60,6 +63,8 @@ public abstract class Tile implements Parcelable {
*/
public ArrayList<UserHandle> userHandle = new ArrayList<>();
+ public HashMap<UserHandle, PendingIntent> pendingIntentMap = new HashMap<>();
+
@VisibleForTesting
long mLastUpdateTime;
private final String mComponentPackage;
@@ -174,7 +179,8 @@ public abstract class Tile implements Parcelable {
* Check whether tile has order.
*/
public boolean hasOrder() {
- return mMetaData.containsKey(META_DATA_KEY_ORDER)
+ return mMetaData != null
+ && mMetaData.containsKey(META_DATA_KEY_ORDER)
&& mMetaData.get(META_DATA_KEY_ORDER) instanceof Integer;
}
@@ -186,13 +192,20 @@ public abstract class Tile implements Parcelable {
}
/**
+ * Check whether tile has a pending intent.
+ */
+ public boolean hasPendingIntent() {
+ return !pendingIntentMap.isEmpty();
+ }
+
+ /**
* Title of the tile that is shown to the user.
*/
public CharSequence getTitle(Context context) {
CharSequence title = null;
ensureMetadataNotStale(context);
final PackageManager packageManager = context.getPackageManager();
- if (mMetaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
+ if (mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
if (mMetaData.containsKey(META_DATA_PREFERENCE_TITLE_URI)) {
// If has as uri to provide dynamic title, skip loading here. UI will later load
// at tile binding time.
@@ -272,10 +285,10 @@ public abstract class Tile implements Parcelable {
* Optional key to use for this tile.
*/
public String getKey(Context context) {
+ ensureMetadataNotStale(context);
if (!hasKey()) {
return null;
}
- ensureMetadataNotStale(context);
if (mMetaData.get(META_DATA_PREFERENCE_KEYHINT) instanceof Integer) {
return context.getResources().getString(mMetaData.getInt(META_DATA_PREFERENCE_KEYHINT));
} else {
@@ -395,6 +408,76 @@ public abstract class Tile implements Parcelable {
return TextUtils.equals(profile, PROFILE_PRIMARY);
}
+ /**
+ * Returns whether the tile belongs to another group / category.
+ */
+ public boolean hasGroupKey() {
+ return mMetaData != null
+ && !TextUtils.isEmpty(mMetaData.getString(META_DATA_PREFERENCE_GROUP_KEY));
+ }
+
+ /**
+ * Returns the group / category key this tile belongs to.
+ */
+ public String getGroupKey() {
+ return (mMetaData == null) ? null : mMetaData.getString(META_DATA_PREFERENCE_GROUP_KEY);
+ }
+
+ /**
+ * The type of the tile.
+ */
+ public enum Type {
+ /**
+ * A preference that can be tapped on to open a new page.
+ */
+ ACTION,
+
+ /**
+ * A preference that can be tapped on to open an external app.
+ */
+ EXTERNAL_ACTION,
+
+ /**
+ * A preference that shows an on / off switch that can be toggled by the user.
+ */
+ SWITCH,
+
+ /**
+ * A preference with both an on / off switch, and a tappable area that can perform an
+ * action.
+ */
+ SWITCH_WITH_ACTION,
+
+ /**
+ * A preference category with a title that can be used to group multiple preferences
+ * together.
+ */
+ GROUP;
+ }
+
+ /**
+ * Returns the type of the tile.
+ *
+ * @see Type
+ */
+ public Type getType() {
+ boolean hasExternalAction = hasPendingIntent();
+ boolean hasAction = hasExternalAction || this instanceof ActivityTile;
+ boolean hasSwitch = hasSwitch();
+
+ if (hasSwitch && hasAction) {
+ return Type.SWITCH_WITH_ACTION;
+ } else if (hasSwitch) {
+ return Type.SWITCH;
+ } else if (hasExternalAction) {
+ return Type.EXTERNAL_ACTION;
+ } else if (hasAction) {
+ return Type.ACTION;
+ } else {
+ return Type.GROUP;
+ }
+ }
+
public static final Comparator<Tile> TILE_COMPARATOR =
(lhs, rhs) -> rhs.getOrder() - lhs.getOrder();
}
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
index acc0087f6dcf..e46db75f633e 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
@@ -113,6 +113,12 @@ public class TileUtils {
public static final String META_DATA_PREFERENCE_KEYHINT = "com.android.settings.keyhint";
/**
+ * Name of the meta-data item that can be set in the AndroidManifest.xml or in the content
+ * provider to specify the key of a group / category where this preference belongs to.
+ */
+ public static final String META_DATA_PREFERENCE_GROUP_KEY = "com.android.settings.group_key";
+
+ /**
* Order of the item that should be displayed on screen. Bigger value items displays closer on
* top.
*/
@@ -202,6 +208,13 @@ public class TileUtils {
"com.android.settings.switch_uri";
/**
+ * Name of the meta-data item that can be set from the content provider providing the intent
+ * that will be executed when the user taps on the preference.
+ */
+ public static final String META_DATA_PREFERENCE_PENDING_INTENT =
+ "com.android.settings.pending_intent";
+
+ /**
* Value for {@link #META_DATA_KEY_PROFILE}. When the device has a managed profile,
* the app will always be run in the primary profile.
*
@@ -331,12 +344,12 @@ public class TileUtils {
continue;
}
final ProviderInfo providerInfo = resolved.providerInfo;
- final List<Bundle> switchData = getSwitchDataFromProvider(context,
+ final List<Bundle> entryData = getEntryDataFromProvider(context,
providerInfo.authority);
- if (switchData == null || switchData.isEmpty()) {
+ if (entryData == null || entryData.isEmpty()) {
continue;
}
- for (Bundle metaData : switchData) {
+ for (Bundle metaData : entryData) {
loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData,
providerInfo);
}
@@ -386,27 +399,43 @@ public class TileUtils {
if (!tile.userHandle.contains(user)) {
tile.userHandle.add(user);
}
+ if (metaData.containsKey(META_DATA_PREFERENCE_PENDING_INTENT)) {
+ tile.pendingIntentMap.put(
+ user, metaData.getParcelable(META_DATA_PREFERENCE_PENDING_INTENT));
+ }
if (!outTiles.contains(tile)) {
outTiles.add(tile);
}
}
- /** Returns the switch data of the key specified from the provider */
+ /** Returns the entry data of the key specified from the provider */
// TODO(b/144732809): rearrange methods by access level modifiers
- static Bundle getSwitchDataFromProvider(Context context, String authority, String key) {
+ static Bundle getEntryDataFromProvider(Context context, String authority, String key) {
final Map<String, IContentProvider> providerMap = new ArrayMap<>();
- final Uri uri = buildUri(authority, SwitchesProvider.METHOD_GET_SWITCH_DATA, key);
- return getBundleFromUri(context, uri, providerMap, null /* bundle */);
+ final Uri uri = buildUri(authority, EntriesProvider.METHOD_GET_ENTRY_DATA, key);
+ Bundle result = getBundleFromUri(context, uri, providerMap, null /* bundle */);
+ if (result == null) {
+ Uri fallbackUri = buildUri(authority, EntriesProvider.METHOD_GET_SWITCH_DATA, key);
+ result = getBundleFromUri(context, fallbackUri, providerMap, null /* bundle */);
+ }
+ return result;
}
- /** Returns all switch data from the provider */
- private static List<Bundle> getSwitchDataFromProvider(Context context, String authority) {
+ /** Returns all entry data from the provider */
+ private static List<Bundle> getEntryDataFromProvider(Context context, String authority) {
final Map<String, IContentProvider> providerMap = new ArrayMap<>();
- final Uri uri = buildUri(authority, SwitchesProvider.METHOD_GET_SWITCH_DATA);
+ final Uri uri = buildUri(authority, EntriesProvider.METHOD_GET_ENTRY_DATA);
final Bundle result = getBundleFromUri(context, uri, providerMap, null /* bundle */);
- return result != null
- ? result.getParcelableArrayList(SwitchesProvider.EXTRA_SWITCH_DATA)
- : null;
+ if (result != null) {
+ return result.getParcelableArrayList(EntriesProvider.EXTRA_ENTRY_DATA);
+ } else {
+ Uri fallbackUri = buildUri(authority, EntriesProvider.METHOD_GET_SWITCH_DATA);
+ Bundle fallbackResult =
+ getBundleFromUri(context, fallbackUri, providerMap, null /* bundle */);
+ return fallbackResult != null
+ ? fallbackResult.getParcelableArrayList(EntriesProvider.EXTRA_SWITCH_DATA)
+ : null;
+ }
}
/**
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 058065eeb606..bac6306260bd 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -519,12 +519,9 @@
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Daugiau laiko."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Mažiau laiko."</string>
<string name="cancel" msgid="5665114069455378395">"Atšaukti"</string>
- <!-- no translation found for next (2699398661093607009) -->
- <skip />
- <!-- no translation found for back (5554327870352703710) -->
- <skip />
- <!-- no translation found for save (3745809743277153149) -->
- <skip />
+ <string name="next" msgid="2699398661093607009">"Kitas"</string>
+ <string name="back" msgid="5554327870352703710">"Atgal"</string>
+ <string name="save" msgid="3745809743277153149">"Išsaugoti"</string>
<string name="okay" msgid="949938843324579502">"Gerai"</string>
<string name="done" msgid="381184316122520313">"Atlikta"</string>
<string name="alarms_and_reminders_label" msgid="6918395649731424294">"Signalai ir priminimai"</string>
@@ -579,12 +576,9 @@
<string name="user_add_user_title" msgid="5457079143694924885">"Pridėti naują naudotoją?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"Galite bendrinti šį įrenginį su kitais žmonėmis sukūrę papildomų naudotojų. Kiekvienam naudotojui suteikiama atskira erdvė, kurią jie gali tinkinti naudodami programas, ekrano foną ir kt. Be to, naudotojai gali koreguoti įrenginio nustatymus, pvz., „Wi‑Fi“, kurie taikomi visiems.\n\nKai pridedate naują naudotoją, šis asmuo turi nusistatyti savo erdvę.\n\nBet kuris naudotojas gali atnaujinti visų kitų naudotojų programas. Pasiekiamumo nustatymai ir paslaugos gali nebūti perkeltos naujam naudotojui."</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"Kai pridedate naują naudotoją, šis asmuo turi nustatyti savo vietą.\n\nBet kuris naudotojas gali atnaujinti visų kitų naudotojų programas."</string>
- <!-- no translation found for user_grant_admin_title (5157031020083343984) -->
- <skip />
- <!-- no translation found for user_grant_admin_message (1673791931033486709) -->
- <skip />
- <!-- no translation found for user_grant_admin_button (5441486731331725756) -->
- <skip />
+ <string name="user_grant_admin_title" msgid="5157031020083343984">"Nustatyti šį naudotoją kaip administratorių?"</string>
+ <string name="user_grant_admin_message" msgid="1673791931033486709">"Administratoriai turi specialių privilegijų, kurių kiti naudotojai neturi. Administratorius gali tvarkyti visus naudotojus, atnaujinti ar iš naujo nustatyti šį įrenginį, keisti nustatymus, peržiūrėti visas įdiegtas programas ir suteikti administratoriaus privilegijas kitiems naudotojams arba jas panaikinti."</string>
+ <string name="user_grant_admin_button" msgid="5441486731331725756">"Nustatyti kaip administratorių"</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"Nustatyti naudotoją dabar?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"Įsitikinkite, kad asmuo gali paimti įrenginį ir nustatyti savo vietą"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"Nustatyti profilį dabar?"</string>
@@ -616,10 +610,8 @@
<string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Bus pradėta nauja svečio sesija ir iš esamos sesijos bus ištrintos visos programos ir duomenys"</string>
<string name="guest_exit_dialog_title" msgid="1846494656849381804">"Išeiti iš svečio režimo?"</string>
<string name="guest_exit_dialog_message" msgid="1743218864242719783">"Bus ištrintos esamos svečio sesijos programos ir duomenys"</string>
- <!-- no translation found for grant_admin (4323199171790522574) -->
- <skip />
- <!-- no translation found for not_grant_admin (3557849576157702485) -->
- <skip />
+ <string name="grant_admin" msgid="4323199171790522574">"Taip, nustatyti kaip administratorių"</string>
+ <string name="not_grant_admin" msgid="3557849576157702485">"Ne, nenustatyti kaip administratoriaus"</string>
<string name="guest_exit_dialog_button" msgid="1736401897067442044">"Išeiti"</string>
<string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Išsaugoti svečio veiklą?"</string>
<string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Galite išsaugoti esamos sesijos veiklą arba ištrinti visas programas ir duomenis"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 7cf89682dd58..052840d4ce0c 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -519,12 +519,9 @@
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"增加时间。"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"减少时间。"</string>
<string name="cancel" msgid="5665114069455378395">"取消"</string>
- <!-- no translation found for next (2699398661093607009) -->
- <skip />
- <!-- no translation found for back (5554327870352703710) -->
- <skip />
- <!-- no translation found for save (3745809743277153149) -->
- <skip />
+ <string name="next" msgid="2699398661093607009">"继续"</string>
+ <string name="back" msgid="5554327870352703710">"返回"</string>
+ <string name="save" msgid="3745809743277153149">"保存"</string>
<string name="okay" msgid="949938843324579502">"确定"</string>
<string name="done" msgid="381184316122520313">"完成"</string>
<string name="alarms_and_reminders_label" msgid="6918395649731424294">"闹钟和提醒"</string>
@@ -579,12 +576,9 @@
<string name="user_add_user_title" msgid="5457079143694924885">"要添加新用户吗?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"创建新用户后,您就能够与其他人共用此设备。每位用户都有自己的专属空间,而且在自己的个人空间内还可以自行安装自己想要的应用、设置壁纸等。此外,用户还可以调整会影响所有用户的设备设置(例如 WLAN 设置)。\n\n当您添加新用户后,该用户需要自行设置个人空间。\n\n任何用户都可以为所有其他用户更新应用。无障碍功能设置和服务可能无法转移给新用户。"</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"当您添加新用户后,该用户需要自行设置个人空间。\n\n任何用户都可以为所有其他用户更新应用。"</string>
- <!-- no translation found for user_grant_admin_title (5157031020083343984) -->
- <skip />
- <!-- no translation found for user_grant_admin_message (1673791931033486709) -->
- <skip />
- <!-- no translation found for user_grant_admin_button (5441486731331725756) -->
- <skip />
+ <string name="user_grant_admin_title" msgid="5157031020083343984">"将此用户设为管理员?"</string>
+ <string name="user_grant_admin_message" msgid="1673791931033486709">"管理员拥有其他用户没有的特殊权限。管理员可以管理所有用户、更新或重置此设备、修改设置、查看所有已安装的应用,以及授予或撤消其他用户的管理员权限。"</string>
+ <string name="user_grant_admin_button" msgid="5441486731331725756">"设为管理员"</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"要现在设置该用户吗?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"请让相应用户操作设备并设置他们自己的空间。"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"要立即设置个人资料吗?"</string>
@@ -616,10 +610,8 @@
<string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"此操作会开始新的访客会话,并删除当前会话中的所有应用和数据"</string>
<string name="guest_exit_dialog_title" msgid="1846494656849381804">"要退出访客模式吗?"</string>
<string name="guest_exit_dialog_message" msgid="1743218864242719783">"此操作会删除当前访客会话中的所有应用和数据"</string>
- <!-- no translation found for grant_admin (4323199171790522574) -->
- <skip />
- <!-- no translation found for not_grant_admin (3557849576157702485) -->
- <skip />
+ <string name="grant_admin" msgid="4323199171790522574">"是,将其设为管理员"</string>
+ <string name="not_grant_admin" msgid="3557849576157702485">"不,不要将其设为管理员"</string>
<string name="guest_exit_dialog_button" msgid="1736401897067442044">"退出"</string>
<string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"要保存访客活动记录吗?"</string>
<string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"您可以保存当前会话中的活动记录,也可以删除所有应用和数据"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 2e6bb535a8f0..f522fd13c9f8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -583,7 +583,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
*/
public void setName(String name) {
// Prevent getName() to be set to null if setName(null) is called
- if (name == null || TextUtils.equals(name, getName())) {
+ if (TextUtils.isEmpty(name) || TextUtils.equals(name, getName())) {
return;
}
mDevice.setAlias(name);
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
index 09abc394634a..9ee8a32fdc77 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
@@ -209,8 +209,7 @@ public class MetricsFeatureProvider {
}
final ComponentName cn = intent.getComponent();
final String key = cn != null ? cn.flattenToString() : intent.getAction();
- return logSettingsTileClick(key + (isWorkProfile ? "/work" : "/personal"),
- sourceMetricsCategory);
+ return logSettingsTileClickWithProfile(key, sourceMetricsCategory, isWorkProfile);
}
/**
@@ -226,4 +225,20 @@ public class MetricsFeatureProvider {
clicked(sourceMetricsCategory, logKey);
return true;
}
+
+ /**
+ * Logs an event when the setting key is clicked with a specific profile from Profile select
+ * dialog.
+ *
+ * @return true if the key is loggable, otherwise false
+ */
+ public boolean logSettingsTileClickWithProfile(String logKey, int sourceMetricsCategory,
+ boolean isWorkProfile) {
+ if (TextUtils.isEmpty(logKey)) {
+ // Not loggable
+ return false;
+ }
+ clicked(sourceMetricsCategory, logKey + (isWorkProfile ? "/work" : "/personal"));
+ return true;
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 6444f3bd4341..4b61ff1177bd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -1015,6 +1015,13 @@ public class CachedBluetoothDeviceTest {
}
@Test
+ public void setName_setDeviceNameIsEmpty() {
+ mCachedDevice.setName("");
+
+ verify(mDevice, never()).setAlias(any());
+ }
+
+ @Test
public void getProfileConnectionState_nullProfile_returnDisconnected() {
assertThat(mCachedDevice.getProfileConnectionState(null)).isEqualTo(
BluetoothProfile.STATE_DISCONNECTED);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
index 3352d86b2dcc..dd8d54a62ff4 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
@@ -203,4 +203,24 @@ public class MetricsFeatureProviderTest {
assertThat(loggable).isFalse();
verifyNoMoreInteractions(mLogWriter);
}
+
+ @Test
+ public void logSettingsTileClickWithProfile_isPersonalProfile_shouldTagPersonal() {
+ final String key = "abc";
+ final boolean loggable = mProvider.logSettingsTileClickWithProfile(key,
+ MetricsEvent.SETTINGS_GESTURES, false);
+
+ assertThat(loggable).isTrue();
+ verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, "abc/personal");
+ }
+
+ @Test
+ public void logSettingsTileClickWithProfile_isWorkProfile_shouldTagWork() {
+ final String key = "abc";
+ final boolean loggable = mProvider.logSettingsTileClickWithProfile(key,
+ MetricsEvent.SETTINGS_GESTURES, true);
+
+ assertThat(loggable).isTrue();
+ verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, "abc/work");
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java
index aa6b0bf33b69..4d2b1ae2ade0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java
@@ -17,19 +17,23 @@ package com.android.settingslib.drawer;
import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_PROFILE;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_GROUP_KEY;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
import static com.android.settingslib.drawer.TileUtils.PROFILE_PRIMARY;
import static com.google.common.truth.Truth.assertThat;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
+import android.os.UserHandle;
import org.junit.Before;
import org.junit.Test;
@@ -191,4 +195,65 @@ public class ActivityTileTest {
assertThat(tile.getTitle(RuntimeEnvironment.application)).isNull();
}
+
+ @Test
+ public void hasPendingIntent_empty_returnsFalse() {
+ final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+ assertThat(tile.hasPendingIntent()).isFalse();
+ }
+
+ @Test
+ public void hasPendingIntent_notEmpty_returnsTrue() {
+ final Tile tile = new ActivityTile(mActivityInfo, "category");
+ tile.pendingIntentMap.put(
+ UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));
+
+ assertThat(tile.hasPendingIntent()).isTrue();
+ }
+
+ @Test
+ public void hasGroupKey_empty_returnsFalse() {
+ final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+ assertThat(tile.hasGroupKey()).isFalse();
+ }
+
+ @Test
+ public void hasGroupKey_notEmpty_returnsTrue() {
+ mActivityInfo.metaData.putString(META_DATA_PREFERENCE_GROUP_KEY, "test_key");
+ final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+ assertThat(tile.hasGroupKey()).isTrue();
+ }
+
+ @Test
+ public void getGroupKey_empty_returnsNull() {
+ final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+ assertThat(tile.getGroupKey()).isNull();
+ }
+
+ @Test
+ public void getGroupKey_notEmpty_returnsValue() {
+ mActivityInfo.metaData.putString(META_DATA_PREFERENCE_GROUP_KEY, "test_key");
+ final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+ assertThat(tile.getGroupKey()).isEqualTo("test_key");
+ }
+
+ @Test
+ public void getType_withoutSwitch_returnsAction() {
+ final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+ assertThat(tile.getType()).isEqualTo(Tile.Type.ACTION);
+ }
+
+ @Test
+ public void getType_withSwitch_returnsSwitchWithAction() {
+ mActivityInfo.metaData.putString(META_DATA_PREFERENCE_SWITCH_URI, "test://testabc/");
+ final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+ assertThat(tile.getType()).isEqualTo(Tile.Type.SWITCH_WITH_ACTION);
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/EntriesProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/EntriesProviderTest.java
new file mode 100644
index 000000000000..a2483305c94a
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/EntriesProviderTest.java
@@ -0,0 +1,472 @@
+/*
+ * 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.settingslib.drawer;
+
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_ENTRY_DATA;
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_SWITCH_CHECKED_STATE;
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_SWITCH_DATA;
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_SWITCH_SET_CHECKED_ERROR;
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_DYNAMIC_SUMMARY;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_DYNAMIC_TITLE;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_ENTRY_DATA;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_PROVIDER_ICON;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_SWITCH_DATA;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_IS_CHECKED;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_ON_CHECKED_CHANGED;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_PENDING_INTENT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ProviderInfo;
+import android.os.Bundle;
+
+import com.android.settingslib.drawer.EntryController.MetaData;
+import com.android.settingslib.drawer.PrimarySwitchControllerTest.TestPrimarySwitchController;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class EntriesProviderTest {
+
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
+ private Context mContext;
+ private ProviderInfo mProviderInfo;
+
+ private TestEntriesProvider mEntriesProvider;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mEntriesProvider = new TestEntriesProvider();
+ mProviderInfo = new ProviderInfo();
+ mProviderInfo.authority = "auth";
+ }
+
+ @Test
+ public void attachInfo_noController_shouldThrowIllegalArgumentException() {
+ thrown.expect(IllegalArgumentException.class);
+
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+ }
+
+ @Test
+ public void attachInfo_NoKeyInController_shouldThrowNullPointerException() {
+ thrown.expect(NullPointerException.class);
+ final TestEntryController controller = new TestEntryController();
+ mEntriesProvider.addController(controller);
+
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+ }
+
+ @Test
+ public void attachInfo_NoMetaDataInController_shouldThrowNullPointerException() {
+ thrown.expect(NullPointerException.class);
+ final TestEntryController controller = new TestEntryController();
+ controller.setKey("123");
+ mEntriesProvider.addController(controller);
+
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+ }
+
+ @Test
+ public void attachInfo_duplicateKey_shouldThrowIllegalArgumentException() {
+ thrown.expect(IllegalArgumentException.class);
+ final TestEntryController controller1 = new TestEntryController();
+ final TestEntryController controller2 = new TestEntryController();
+ controller1.setKey("123");
+ controller2.setKey("123");
+ controller1.setMetaData(new MetaData("category"));
+ controller2.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller1);
+ mEntriesProvider.addController(controller2);
+
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+ }
+
+ @Test
+ public void attachInfo_hasDifferentControllers_shouldNotThrowException() {
+ final TestEntryController controller1 = new TestEntryController();
+ final TestEntryController controller2 = new TestEntryController();
+ controller1.setKey("123");
+ controller2.setKey("456");
+ controller1.setMetaData(new MetaData("category"));
+ controller2.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller1);
+ mEntriesProvider.addController(controller2);
+
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+ }
+
+ @Test
+ public void getEntryData_shouldNotReturnPrimarySwitchData() {
+ final EntryController controller = new TestPrimarySwitchController("123");
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle switchData = mEntriesProvider.call(METHOD_GET_ENTRY_DATA, "uri",
+ null /* extras*/);
+
+ final ArrayList<Bundle> dataList = switchData.getParcelableArrayList(EXTRA_ENTRY_DATA);
+ assertThat(dataList).isEmpty();
+ }
+
+ @Test
+ public void getEntryData_shouldReturnDataList() {
+ final TestEntryController controller = new TestEntryController();
+ final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category").setPendingIntent(pendingIntent));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle entryData = mEntriesProvider.call(METHOD_GET_ENTRY_DATA, "uri",
+ null /* extras*/);
+
+ final ArrayList<Bundle> dataList = entryData.getParcelableArrayList(EXTRA_ENTRY_DATA);
+ assertThat(dataList).hasSize(1);
+ assertThat(dataList.get(0).getString(META_DATA_PREFERENCE_KEYHINT)).isEqualTo("123");
+ assertThat(dataList.get(0).getParcelable(META_DATA_PREFERENCE_PENDING_INTENT,
+ PendingIntent.class))
+ .isEqualTo(pendingIntent);
+ }
+
+ @Test
+ public void getSwitchData_shouldReturnDataList() {
+ final TestEntryController controller = new TestEntryController();
+ final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category").setPendingIntent(pendingIntent));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle entryData = mEntriesProvider.call(METHOD_GET_SWITCH_DATA, "uri",
+ null /* extras*/);
+
+ final ArrayList<Bundle> dataList = entryData.getParcelableArrayList(EXTRA_SWITCH_DATA);
+ assertThat(dataList).hasSize(1);
+ assertThat(dataList.get(0).getString(META_DATA_PREFERENCE_KEYHINT)).isEqualTo("123");
+ assertThat(dataList.get(0).getParcelable(META_DATA_PREFERENCE_PENDING_INTENT,
+ PendingIntent.class))
+ .isEqualTo(pendingIntent);
+ }
+
+ @Test
+ public void getEntryDataByKey_shouldReturnData() {
+ final Bundle extras = new Bundle();
+ extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+ final TestEntryController controller = new TestEntryController();
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle entryData = mEntriesProvider.call(METHOD_GET_ENTRY_DATA, "uri", extras);
+
+ assertThat(entryData.getString(META_DATA_PREFERENCE_KEYHINT)).isEqualTo("123");
+ }
+
+ @Test
+ public void getSwitchDataByKey_shouldReturnData() {
+ final Bundle extras = new Bundle();
+ extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+ final TestEntryController controller = new TestEntryController();
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle entryData = mEntriesProvider.call(METHOD_GET_SWITCH_DATA, "uri", extras);
+
+ assertThat(entryData.getString(META_DATA_PREFERENCE_KEYHINT)).isEqualTo("123");
+ }
+
+ @Test
+ public void isSwitchChecked_shouldReturnCheckedState() {
+ final Bundle extras = new Bundle();
+ extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+ final TestSwitchController controller = new TestSwitchController();
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ controller.setSwitchChecked(true);
+ Bundle result = mEntriesProvider.call(METHOD_IS_CHECKED, "uri", extras);
+
+ assertThat(result.getBoolean(EXTRA_SWITCH_CHECKED_STATE)).isTrue();
+
+ controller.setSwitchChecked(false);
+ result = mEntriesProvider.call(METHOD_IS_CHECKED, "uri", extras);
+
+ assertThat(result.getBoolean(EXTRA_SWITCH_CHECKED_STATE)).isFalse();
+ }
+
+ @Test
+ public void getProviderIcon_noImplementInterface_shouldReturnNull() {
+ final Bundle extras = new Bundle();
+ extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+ final TestEntryController controller = new TestEntryController();
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle iconBundle = mEntriesProvider.call(METHOD_GET_PROVIDER_ICON, "uri", extras);
+
+ assertThat(iconBundle).isNull();
+ }
+
+ @Test
+ public void getProviderIcon_implementInterface_shouldReturnIcon() {
+ final Bundle extras = new Bundle();
+ extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+ final TestEntryController controller = new TestDynamicController();
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle iconBundle = mEntriesProvider.call(METHOD_GET_PROVIDER_ICON, "uri", extras);
+
+ assertThat(iconBundle).isEqualTo(TestDynamicController.ICON_BUNDLE);
+ }
+
+ @Test
+ public void getDynamicTitle_noImplementInterface_shouldReturnNull() {
+ final Bundle extras = new Bundle();
+ extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+ final TestEntryController controller = new TestEntryController();
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle result = mEntriesProvider.call(METHOD_GET_DYNAMIC_TITLE, "uri", extras);
+
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void getDynamicTitle_implementInterface_shouldReturnTitle() {
+ final Bundle extras = new Bundle();
+ extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+ final TestEntryController controller = new TestDynamicController();
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle result = mEntriesProvider.call(METHOD_GET_DYNAMIC_TITLE, "uri", extras);
+
+ assertThat(result.getString(META_DATA_PREFERENCE_TITLE))
+ .isEqualTo(TestDynamicController.TITLE);
+ }
+
+ @Test
+ public void getDynamicSummary_noImplementInterface_shouldReturnNull() {
+ final Bundle extras = new Bundle();
+ extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+ final TestEntryController controller = new TestEntryController();
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle result = mEntriesProvider.call(METHOD_GET_DYNAMIC_SUMMARY, "uri", extras);
+
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void getDynamicSummary_implementInterface_shouldReturnSummary() {
+ final Bundle extras = new Bundle();
+ extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+ final TestEntryController controller = new TestDynamicController();
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle result = mEntriesProvider.call(METHOD_GET_DYNAMIC_SUMMARY, "uri", extras);
+
+ assertThat(result.getString(META_DATA_PREFERENCE_SUMMARY))
+ .isEqualTo(TestDynamicController.SUMMARY);
+ }
+
+ @Test
+ public void onSwitchCheckedChangedSuccess_shouldReturnNoError() {
+ final Bundle extras = new Bundle();
+ extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+ final TestSwitchController controller = new TestSwitchController();
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category"));
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle result = mEntriesProvider.call(METHOD_ON_CHECKED_CHANGED, "uri", extras);
+
+ assertThat(result.getBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR)).isFalse();
+ }
+
+ @Test
+ public void onSwitchCheckedChangedFailed_shouldReturnErrorMessage() {
+ final Bundle extras = new Bundle();
+ extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+ final TestSwitchController controller = new TestSwitchController();
+ controller.setKey("123");
+ controller.setMetaData(new MetaData("category"));
+ controller.setSwitchErrorMessage("error");
+ mEntriesProvider.addController(controller);
+ mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+ final Bundle result = mEntriesProvider.call(METHOD_ON_CHECKED_CHANGED, "uri", extras);
+
+ assertThat(result.getBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR)).isTrue();
+ assertThat(result.getString(EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE)).isEqualTo("error");
+ }
+
+ private static class TestEntriesProvider extends EntriesProvider {
+
+ private List<EntryController> mControllers;
+
+ @Override
+ protected List<EntryController> createEntryControllers() {
+ return mControllers;
+ }
+
+ void addController(EntryController controller) {
+ if (mControllers == null) {
+ mControllers = new ArrayList<>();
+ }
+ mControllers.add(controller);
+ }
+ }
+
+ private static class TestEntryController extends EntryController {
+
+ private String mKey;
+ private MetaData mMetaData;
+
+ @Override
+ public String getKey() {
+ return mKey;
+ }
+
+ @Override
+ protected MetaData getMetaData() {
+ return mMetaData;
+ }
+
+ void setKey(String key) {
+ mKey = key;
+ }
+
+ void setMetaData(MetaData metaData) {
+ mMetaData = metaData;
+ }
+ }
+
+ private static class TestSwitchController extends EntryController implements ProviderSwitch {
+
+ private String mKey;
+ private MetaData mMetaData;
+ private boolean mChecked;
+ private String mErrorMsg;
+
+ @Override
+ public String getKey() {
+ return mKey;
+ }
+
+ @Override
+ protected MetaData getMetaData() {
+ return mMetaData;
+ }
+
+ @Override
+ public boolean isSwitchChecked() {
+ return mChecked;
+ }
+
+ @Override
+ public boolean onSwitchCheckedChanged(boolean checked) {
+ return mErrorMsg == null ? true : false;
+ }
+
+ @Override
+ public String getSwitchErrorMessage(boolean attemptedChecked) {
+ return mErrorMsg;
+ }
+
+ void setKey(String key) {
+ mKey = key;
+ }
+
+ void setMetaData(MetaData metaData) {
+ mMetaData = metaData;
+ }
+
+ void setSwitchChecked(boolean checked) {
+ mChecked = checked;
+ }
+
+ void setSwitchErrorMessage(String errorMsg) {
+ mErrorMsg = errorMsg;
+ }
+ }
+
+ private static class TestDynamicController extends TestEntryController
+ implements ProviderIcon, DynamicTitle, DynamicSummary {
+
+ static final String TITLE = "title";
+ static final String SUMMARY = "summary";
+ static final Bundle ICON_BUNDLE = new Bundle();
+
+ @Override
+ public Bundle getProviderIcon() {
+ return ICON_BUNDLE;
+ }
+
+ @Override
+ public String getDynamicTitle() {
+ return TITLE;
+ }
+
+ @Override
+ public String getDynamicSummary() {
+ return SUMMARY;
+ }
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java
index abfb407d749e..80f9efb8b5ac 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java
@@ -17,20 +17,24 @@ package com.android.settingslib.drawer;
import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_PROFILE;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_GROUP_KEY;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
import static com.android.settingslib.drawer.TileUtils.PROFILE_PRIMARY;
import static com.google.common.truth.Truth.assertThat;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
+import android.os.UserHandle;
import org.junit.Before;
import org.junit.Rule;
@@ -173,13 +177,93 @@ public class ProviderTileTest {
assertThat(tile.mLastUpdateTime).isNotEqualTo(staleTimeStamp);
}
+ @Test
+ public void hasPendingIntent_empty_returnsFalse() {
+ final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+ assertThat(tile.hasPendingIntent()).isFalse();
+ }
+
+ @Test
+ public void hasPendingIntent_notEmpty_returnsTrue() {
+ final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+ tile.pendingIntentMap.put(
+ UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));
+
+ assertThat(tile.hasPendingIntent()).isTrue();
+ }
+
+ @Test
+ public void hasGroupKey_empty_returnsFalse() {
+ final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+ assertThat(tile.hasGroupKey()).isFalse();
+ }
+
+ @Test
+ public void hasGroupKey_notEmpty_returnsTrue() {
+ mMetaData.putString(META_DATA_PREFERENCE_GROUP_KEY, "test_key");
+ final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+ assertThat(tile.hasGroupKey()).isTrue();
+ }
+
+ @Test
+ public void getGroupKey_empty_returnsNull() {
+ final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+ assertThat(tile.getGroupKey()).isNull();
+ }
+
+ @Test
+ public void getGroupKey_notEmpty_returnsValue() {
+ mMetaData.putString(META_DATA_PREFERENCE_GROUP_KEY, "test_key");
+ final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+ assertThat(tile.getGroupKey()).isEqualTo("test_key");
+ }
+
+ @Test
+ public void getType_withSwitch_returnsSwitch() {
+ mMetaData.putString(META_DATA_PREFERENCE_SWITCH_URI, "test://testabc/");
+ final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+ assertThat(tile.getType()).isEqualTo(Tile.Type.SWITCH);
+ }
+
+ @Test
+ public void getType_withSwitchAndPendingIntent_returnsSwitchWithAction() {
+ mMetaData.putString(META_DATA_PREFERENCE_SWITCH_URI, "test://testabc/");
+ final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+ tile.pendingIntentMap.put(
+ UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));
+
+ assertThat(tile.getType()).isEqualTo(Tile.Type.SWITCH_WITH_ACTION);
+ }
+
+ @Test
+ public void getType_withPendingIntent_returnsExternalAction() {
+ final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+ tile.pendingIntentMap.put(
+ UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));
+
+ assertThat(tile.getType()).isEqualTo(Tile.Type.EXTERNAL_ACTION);
+ }
+
+ @Test
+ public void getType_withoutSwitchAndPendingIntent_returnsGroup() {
+ final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+ assertThat(tile.getType()).isEqualTo(Tile.Type.GROUP);
+ }
+
@Implements(TileUtils.class)
private static class ShadowTileUtils {
private static Bundle sMetaData;
@Implementation
- protected static Bundle getSwitchDataFromProvider(Context context, String authority,
+ protected static Bundle getEntryDataFromProvider(Context context, String authority,
String key) {
return sMetaData;
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index 906e06e81e2b..20864664e512 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -21,6 +21,7 @@ import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_PROFILE;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_PENDING_INTENT;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
@@ -40,6 +41,7 @@ import static org.mockito.Mockito.when;
import static org.robolectric.RuntimeEnvironment.application;
import android.app.ActivityManager;
+import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -350,6 +352,53 @@ public class TileUtilsTest {
assertThat(outTiles).isEmpty();
}
+ @Test
+ public void loadTilesForAction_multipleUserProfiles_updatesUserHandle() {
+ Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+ List<Tile> outTiles = new ArrayList<>();
+ List<ResolveInfo> info = new ArrayList<>();
+ ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
+ URI_GET_SUMMARY, null, 123, PROFILE_ALL);
+ info.add(resolveInfo);
+
+ when(mPackageManager.queryIntentActivitiesAsUser(any(Intent.class), anyInt(), anyInt()))
+ .thenReturn(info);
+
+ TileUtils.loadTilesForAction(mContext, UserHandle.CURRENT, IA_SETTINGS_ACTION,
+ addedCache, null /* defaultCategory */, outTiles, false /* requiresSettings */);
+ TileUtils.loadTilesForAction(mContext, new UserHandle(10), IA_SETTINGS_ACTION,
+ addedCache, null /* defaultCategory */, outTiles, false /* requiresSettings */);
+
+ assertThat(outTiles).hasSize(1);
+ assertThat(outTiles.get(0).userHandle)
+ .containsExactly(UserHandle.CURRENT, new UserHandle(10));
+ }
+
+ @Test
+ public void loadTilesForAction_withPendingIntent_updatesPendingIntentMap() {
+ Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+ List<Tile> outTiles = new ArrayList<>();
+ List<ResolveInfo> info = new ArrayList<>();
+ ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
+ URI_GET_SUMMARY, null, 123, PROFILE_ALL);
+ PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ resolveInfo.activityInfo.metaData
+ .putParcelable(META_DATA_PREFERENCE_PENDING_INTENT, pendingIntent);
+ info.add(resolveInfo);
+
+ when(mPackageManager.queryIntentActivitiesAsUser(any(Intent.class), anyInt(), anyInt()))
+ .thenReturn(info);
+
+ TileUtils.loadTilesForAction(mContext, UserHandle.CURRENT, IA_SETTINGS_ACTION,
+ addedCache, null /* defaultCategory */, outTiles, false /* requiresSettings */);
+ TileUtils.loadTilesForAction(mContext, new UserHandle(10), IA_SETTINGS_ACTION,
+ addedCache, null /* defaultCategory */, outTiles, false /* requiresSettings */);
+
+ assertThat(outTiles).hasSize(1);
+ assertThat(outTiles.get(0).pendingIntentMap).containsExactly(
+ UserHandle.CURRENT, pendingIntent, new UserHandle(10), pendingIntent);
+ }
+
public static ResolveInfo newInfo(boolean systemApp, String category) {
return newInfo(systemApp, category, null);
}
@@ -424,7 +473,7 @@ public class TileUtilsTest {
private static Bundle sMetaData;
@Implementation
- protected static List<Bundle> getSwitchDataFromProvider(Context context, String authority) {
+ protected static List<Bundle> getEntryDataFromProvider(Context context, String authority) {
return Arrays.asList(sMetaData);
}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
index 57b3acd6557a..66c54f2a668e 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
@@ -21,6 +21,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/keyguard_lock_padding"
+ android:importantForAccessibility="no"
android:ellipsize="marquee"
android:focusable="true"
android:gravity="center"
diff --git a/packages/SystemUI/res/layout/biometric_prompt_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
index 595cea8b9018..bea0e13c77dc 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
@@ -28,7 +28,6 @@
android:singleLine="true"
android:marqueeRepeatLimit="1"
android:ellipsize="marquee"
- android:importantForAccessibility="no"
style="@style/TextAppearance.AuthCredential.Title"/>
<TextView
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 763930db1d55..261b08d4356f 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -38,4 +38,9 @@
protected. -->
<bool name="flag_battery_shield_icon">false</bool>
+ <!-- Whether face auth will immediately stop when the display state is OFF -->
+ <bool name="flag_stop_face_auth_on_display_off">false</bool>
+
+ <!-- Whether we want to stop pulsing while running the face scanning animation -->
+ <bool name="flag_stop_pulsing_face_scanning_animation">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 070748cb92f8..ccc6f8252e9b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -362,6 +362,8 @@
<string name="biometric_dialog_tap_confirm_with_face_alt_3">Face recognized. Press the unlock icon to continue.</string>
<!-- Talkback string when a biometric is authenticated [CHAR LIMIT=NONE] -->
<string name="biometric_dialog_authenticated">Authenticated</string>
+ <!-- Talkback string when a canceling authentication [CHAR LIMIT=NONE] -->
+ <string name="biometric_dialog_cancel_authentication">Cancel Authentication</string>
<!-- Button text shown on BiometricPrompt giving the user the option to use an alternate form of authentication (Pin) [CHAR LIMIT=30] -->
<string name="biometric_dialog_use_pin">Use PIN</string>
@@ -430,6 +432,8 @@
<string name="face_reenroll_failure_dialog_content">Couldn\u2019t set up face unlock. Go to Settings to try again.</string>
<!-- Message shown when the system-provided fingerprint dialog is shown, asking for authentication -->
<string name="fingerprint_dialog_touch_sensor">Touch the fingerprint sensor</string>
+ <!-- Content description after successful auth when confirmation required -->
+ <string name="fingerprint_dialog_authenticated_confirmation">Press the unlock icon to continue</string>
<!-- Message shown to inform the user a face cannot be recognized and fingerprint should instead be used.[CHAR LIMIT=50] -->
<string name="fingerprint_dialog_use_fingerprint_instead">Can\u2019t recognize face. Use fingerprint instead.</string>
<!-- Message shown to inform the user a face cannot be recognized and fingerprint should instead be used.[CHAR LIMIT=50] -->
@@ -946,6 +950,8 @@
<!-- Message shown when face authentication fails and the pin pad is visible. [CHAR LIMIT=60] -->
<string name="keyguard_retry">Swipe up to try again</string>
+ <!-- Message shown when face authentication fails and the pin pad is visible. [CHAR LIMIT=60] -->
+ <string name="accesssibility_keyguard_retry">Swipe up to try Face Unlock again</string>
<!-- Message shown when notifying user to unlock in order to use NFC. [CHAR LIMIT=60] -->
<string name="require_unlock_for_nfc">Unlock to use NFC</string>
@@ -2563,6 +2569,9 @@
<!-- Tooltip to show in management screen when there are multiple structures [CHAR_LIMIT=50] -->
<string name="controls_structure_tooltip">Swipe to see more</string>
+ <!-- Accessibility action informing the user how they can retry face authentication [CHAR LIMIT=NONE] -->
+ <string name="retry_face">Retry face authentication</string>
+
<!-- Message to tell the user to wait while systemui attempts to load a set of
recommended controls [CHAR_LIMIT=60] -->
<string name="controls_seeding_in_progress">Loading recommendations</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
index d8085b9f9f2e..2abb7a41ef08 100644
--- a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
+++ b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
@@ -20,6 +20,7 @@ import android.annotation.StringDef
import android.os.PowerManager
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.FaceAuthApiRequestReason.Companion.ACCESSIBILITY_ACTION
import com.android.keyguard.FaceAuthApiRequestReason.Companion.NOTIFICATION_PANEL_CLICKED
import com.android.keyguard.FaceAuthApiRequestReason.Companion.PICK_UP_GESTURE_TRIGGERED
import com.android.keyguard.FaceAuthApiRequestReason.Companion.QS_EXPANDED
@@ -32,6 +33,7 @@ import com.android.keyguard.InternalFaceAuthReasons.AUTH_REQUEST_DURING_CANCELLA
import com.android.keyguard.InternalFaceAuthReasons.BIOMETRIC_ENABLED
import com.android.keyguard.InternalFaceAuthReasons.CAMERA_LAUNCHED
import com.android.keyguard.InternalFaceAuthReasons.DEVICE_WOKEN_UP_ON_REACH_GESTURE
+import com.android.keyguard.InternalFaceAuthReasons.DISPLAY_OFF
import com.android.keyguard.InternalFaceAuthReasons.DREAM_STARTED
import com.android.keyguard.InternalFaceAuthReasons.DREAM_STOPPED
import com.android.keyguard.InternalFaceAuthReasons.ENROLLMENTS_CHANGED
@@ -71,6 +73,7 @@ import com.android.keyguard.InternalFaceAuthReasons.USER_SWITCHING
NOTIFICATION_PANEL_CLICKED,
QS_EXPANDED,
PICK_UP_GESTURE_TRIGGERED,
+ ACCESSIBILITY_ACTION,
)
annotation class FaceAuthApiRequestReason {
companion object {
@@ -80,6 +83,7 @@ annotation class FaceAuthApiRequestReason {
const val QS_EXPANDED = "Face auth due to QS expansion."
const val PICK_UP_GESTURE_TRIGGERED =
"Face auth due to pickup gesture triggered when the device is awake and not from AOD."
+ const val ACCESSIBILITY_ACTION = "Face auth due to an accessibility action."
}
}
@@ -128,6 +132,7 @@ private object InternalFaceAuthReasons {
const val NON_STRONG_BIOMETRIC_ALLOWED_CHANGED =
"Face auth stopped because non strong biometric allowed changed"
const val POSTURE_CHANGED = "Face auth started/stopped due to device posture changed."
+ const val DISPLAY_OFF = "Face auth stopped due to display state OFF."
}
/**
@@ -217,7 +222,9 @@ constructor(private val id: Int, val reason: String, var extraInfo: Int = 0) :
@UiEvent(doc = STRONG_AUTH_ALLOWED_CHANGED)
FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED(1255, STRONG_AUTH_ALLOWED_CHANGED),
@UiEvent(doc = NON_STRONG_BIOMETRIC_ALLOWED_CHANGED)
- FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED(1256, NON_STRONG_BIOMETRIC_ALLOWED_CHANGED);
+ FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED(1256, NON_STRONG_BIOMETRIC_ALLOWED_CHANGED),
+ @UiEvent(doc = ACCESSIBILITY_ACTION) FACE_AUTH_ACCESSIBILITY_ACTION(1454, ACCESSIBILITY_ACTION),
+ @UiEvent(doc = DISPLAY_OFF) FACE_AUTH_DISPLAY_OFF(1461, DISPLAY_OFF);
override fun getId(): Int = this.id
@@ -233,6 +240,8 @@ private val apiRequestReasonToUiEvent =
FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED,
QS_EXPANDED to FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED,
PICK_UP_GESTURE_TRIGGERED to FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED,
+ PICK_UP_GESTURE_TRIGGERED to FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED,
+ ACCESSIBILITY_ACTION to FaceAuthUiEvent.FACE_AUTH_ACCESSIBILITY_ACTION,
)
/** Converts the [reason] to the corresponding [FaceAuthUiEvent]. */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
index 461d390fd477..bb799fc55171 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
@@ -27,6 +27,7 @@ data class KeyguardFaceListenModel(
override var userId: Int = 0,
override var listening: Boolean = false,
// keep sorted
+ var allowedDisplayState: Boolean = false,
var alternateBouncerShowing: Boolean = false,
var authInterruptActive: Boolean = false,
var biometricSettingEnabledForUser: Boolean = false,
@@ -57,6 +58,8 @@ data class KeyguardFaceListenModel(
userId.toString(),
listening.toString(),
// keep sorted
+ allowedDisplayState.toString(),
+ alternateBouncerShowing.toString(),
authInterruptActive.toString(),
biometricSettingEnabledForUser.toString(),
bouncerFullyShown.toString(),
@@ -74,7 +77,6 @@ data class KeyguardFaceListenModel(
supportsDetect.toString(),
switchingUser.toString(),
systemUser.toString(),
- alternateBouncerShowing.toString(),
udfpsFingerDown.toString(),
userNotTrustedOrDetectionIsNeeded.toString(),
)
@@ -96,7 +98,9 @@ data class KeyguardFaceListenModel(
userId = model.userId
listening = model.listening
// keep sorted
+ allowedDisplayState = model.allowedDisplayState
alternateBouncerShowing = model.alternateBouncerShowing
+ authInterruptActive = model.authInterruptActive
biometricSettingEnabledForUser = model.biometricSettingEnabledForUser
bouncerFullyShown = model.bouncerFullyShown
faceAndFpNotAuthenticated = model.faceAndFpNotAuthenticated
@@ -105,7 +109,6 @@ data class KeyguardFaceListenModel(
faceLockedOut = model.faceLockedOut
goingToSleep = model.goingToSleep
keyguardAwake = model.keyguardAwake
- goingToSleep = model.goingToSleep
keyguardGoingAway = model.keyguardGoingAway
listeningForFaceAssistant = model.listeningForFaceAssistant
occludingAppRequestingFaceAuth = model.occludingAppRequestingFaceAuth
@@ -140,6 +143,8 @@ data class KeyguardFaceListenModel(
"userId",
"listening",
// keep sorted
+ "allowedDisplayState",
+ "alternateBouncerShowing",
"authInterruptActive",
"biometricSettingEnabledForUser",
"bouncerFullyShown",
@@ -157,7 +162,6 @@ data class KeyguardFaceListenModel(
"supportsDetect",
"switchingUser",
"systemUser",
- "udfpsBouncerShowing",
"udfpsFingerDown",
"userNotTrustedOrDetectionIsNeeded",
)
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index f446e523afa9..8f323bb066c5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -100,6 +100,7 @@ import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.shade.TouchLogger;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -659,6 +660,11 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
}
@Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
+ }
+
+ @Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mViewMediatorCallback != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index e18050d77847..7bab7f39abc7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -68,6 +68,7 @@ import com.android.keyguard.dagger.KeyguardBouncerScope;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
+import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate;
import com.android.systemui.biometrics.SideFpsController;
import com.android.systemui.biometrics.SideFpsUiRequestSource;
import com.android.systemui.classifier.FalsingA11yDelegate;
@@ -388,9 +389,11 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
TelephonyManager telephonyManager,
ViewMediatorCallback viewMediatorCallback,
AudioManager audioManager,
- KeyguardFaceAuthInteractor keyguardFaceAuthInteractor
+ KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
+ FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate
) {
super(view);
+ view.setAccessibilityDelegate(faceAuthAccessibilityDelegate);
mLockPatternUtils = lockPatternUtils;
mUpdateMonitor = keyguardUpdateMonitor;
mSecurityModel = keyguardSecurityModel;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index a6252a39ee8a..26fe2f9e4403 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -23,12 +23,14 @@ import android.graphics.Canvas;
import android.os.Build;
import android.os.Trace;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.widget.GridLayout;
import com.android.systemui.R;
+import com.android.systemui.shade.TouchLogger;
import com.android.systemui.statusbar.CrossFadeHelper;
import java.io.PrintWriter;
@@ -110,6 +112,11 @@ public class KeyguardStatusView extends GridLayout {
}
}
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
+ }
+
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardStatusView:");
pw.println(" mDarkAmount: " + mDarkAmount);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index de24024aaa2f..bd88b78b1bc9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -37,6 +37,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.keyguard.FaceAuthReasonKt.apiRequestReasonToUiEvent;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_DISPLAY_OFF;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_DREAM_STARTED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED;
@@ -131,6 +132,7 @@ import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
+import android.view.Display;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -155,6 +157,8 @@ import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.DumpsysTableLogger;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.shared.constants.TrustAgentUiEvent;
@@ -169,6 +173,7 @@ import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.WeatherData;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -327,6 +332,25 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
};
+ private final DisplayTracker.Callback mDisplayCallback = new DisplayTracker.Callback() {
+ @Override
+ public void onDisplayChanged(int displayId) {
+ if (displayId != Display.DEFAULT_DISPLAY) {
+ return;
+ }
+
+ if (mDisplayTracker.getDisplay(mDisplayTracker.getDefaultDisplayId()).getState()
+ == Display.STATE_OFF) {
+ mAllowedDisplayStateForFaceAuth = false;
+ updateFaceListeningState(
+ BIOMETRIC_ACTION_STOP,
+ FACE_AUTH_DISPLAY_OFF
+ );
+ } else {
+ mAllowedDisplayStateForFaceAuth = true;
+ }
+ }
+ };
private final FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
HashMap<Integer, SimData> mSimDatas = new HashMap<>();
@@ -347,6 +371,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private boolean mOccludingAppRequestingFp;
private boolean mOccludingAppRequestingFace;
private boolean mSecureCameraLaunched;
+ private boolean mAllowedDisplayStateForFaceAuth = true;
@VisibleForTesting
protected boolean mTelephonyCapable;
@@ -391,6 +416,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private final FaceManager mFaceManager;
@Nullable
private KeyguardFaceAuthInteractor mFaceAuthInteractor;
+ private final DisplayTracker mDisplayTracker;
private final LockPatternUtils mLockPatternUtils;
@VisibleForTesting
@DevicePostureInt
@@ -2186,6 +2212,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp");
Assert.isMainThread();
+ mAllowedDisplayStateForFaceAuth = true;
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
if (mFaceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(pmWakeReason)) {
FACE_AUTH_UPDATED_STARTED_WAKING_UP.setExtraInfo(pmWakeReason);
@@ -2338,7 +2365,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@Nullable BiometricManager biometricManager,
FaceWakeUpTriggersConfig faceWakeUpTriggersConfig,
DevicePostureController devicePostureController,
- Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider) {
+ Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider,
+ FeatureFlags featureFlags,
+ DisplayTracker displayTracker) {
mContext = context;
mSubscriptionManager = subscriptionManager;
mUserTracker = userTracker;
@@ -2379,6 +2408,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mConfigFaceAuthSupportedPosture = mContext.getResources().getInteger(
R.integer.config_face_auth_supported_posture);
mFaceWakeUpTriggersConfig = faceWakeUpTriggersConfig;
+ mDisplayTracker = displayTracker;
+ if (featureFlags.isEnabled(Flags.STOP_FACE_AUTH_ON_DISPLAY_OFF)) {
+ mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor);
+ }
mHandler = new Handler(mainLooper) {
@Override
@@ -3136,6 +3169,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
return false;
}
+ if (isFaceAuthInteractorEnabled()) {
+ return mFaceAuthInteractor.canFaceAuthRun();
+ }
+
final boolean statusBarShadeLocked = mStatusBarState == StatusBarState.SHADE_LOCKED;
final boolean awakeKeyguard = isKeyguardVisible() && mDeviceInteractive
&& !statusBarShadeLocked;
@@ -3181,7 +3218,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
&& (!mSecureCameraLaunched || mAlternateBouncerShowing)
&& faceAndFpNotAuthenticated
&& !mGoingToSleep
- && isPostureAllowedForFaceAuth;
+ && isPostureAllowedForFaceAuth
+ && mAllowedDisplayStateForFaceAuth;
// Aggregate relevant fields for debug logging.
logListenerModelData(
@@ -3189,6 +3227,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
System.currentTimeMillis(),
user,
shouldListen,
+ mAllowedDisplayStateForFaceAuth,
mAlternateBouncerShowing,
mAuthInterruptActive,
biometricEnabledForUser,
@@ -4333,6 +4372,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mLockPatternUtils.unregisterStrongAuthTracker(mStrongAuthTracker);
mTrustManager.unregisterTrustListener(this);
+ mDisplayTracker.removeCallback(mDisplayCallback);
mHandler.removeCallbacksAndMessages(null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index 403c80985377..95e2dba88b90 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -36,6 +36,8 @@ import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.Utils
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.util.asIndenting
@@ -54,6 +56,7 @@ class FaceScanningOverlay(
val mainExecutor: Executor,
val logger: ScreenDecorationsLogger,
val authController: AuthController,
+ val featureFlags: FeatureFlags,
) : ScreenDecorations.DisplayCutoutView(context, pos) {
private var showScanningAnim = false
private val rimPaint = Paint()
@@ -294,6 +297,15 @@ class FaceScanningOverlay(
}
private fun createFaceScanningRimAnimator(): AnimatorSet {
+ val dontPulse = featureFlags.isEnabled(Flags.STOP_PULSING_FACE_SCANNING_ANIMATION)
+ if (dontPulse) {
+ return AnimatorSet().apply {
+ playSequentially(
+ cameraProtectionAnimator,
+ createRimAppearAnimator(),
+ )
+ }
+ }
return AnimatorSet().apply {
playSequentially(
cameraProtectionAnimator,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
index e60d4e10f957..0c7d56f46530 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
@@ -100,6 +100,9 @@ class AuthBiometricFaceIconController(
)
} else if (newState == STATE_ERROR && oldState != STATE_ERROR) {
animateIconOnce(R.drawable.face_dialog_dark_to_error)
+ iconView.contentDescription = context.getString(
+ R.string.keyguard_face_failed
+ )
} else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
iconView.contentDescription = context.getString(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index 9807b9e6f1b3..d82f458cbde2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -122,9 +122,7 @@ open class AuthBiometricFingerprintIconController(
if (shouldAnimateIconViewForTransition(lastState, newState)) {
iconView.playAnimation()
}
- if (isSideFps) {
- LottieColorUtils.applyDynamicColors(context, iconView)
- }
+ LottieColorUtils.applyDynamicColors(context, iconView)
}
override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) {
@@ -142,13 +140,18 @@ open class AuthBiometricFingerprintIconController(
STATE_IDLE,
STATE_AUTHENTICATING_ANIMATING_IN,
STATE_AUTHENTICATING,
- STATE_PENDING_CONFIRMATION,
STATE_AUTHENTICATED ->
if (isSideFps) {
R.string.security_settings_sfps_enroll_find_sensor_message
} else {
R.string.fingerprint_dialog_touch_sensor
}
+ STATE_PENDING_CONFIRMATION ->
+ if (isSideFps) {
+ R.string.security_settings_sfps_enroll_find_sensor_message
+ } else {
+ R.string.fingerprint_dialog_authenticated_confirmation
+ }
STATE_ERROR,
STATE_HELP -> R.string.biometric_dialog_try_again
else -> null
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index cd8f04d18500..ed4b91c7c4e4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -737,7 +737,7 @@ public abstract class AuthBiometricView extends LinearLayout implements AuthBiom
});
mUseCredentialButton.setOnClickListener((view) -> {
- startTransitionToCredentialUI();
+ startTransitionToCredentialUI(false /* isError */);
});
mConfirmButton.setOnClickListener((view) -> {
@@ -768,9 +768,12 @@ public abstract class AuthBiometricView extends LinearLayout implements AuthBiom
/**
* Kicks off the animation process and invokes the callback.
+ *
+ * @param isError if this was triggered due to an error and not a user action (unused,
+ * previously for haptics).
*/
@Override
- public void startTransitionToCredentialUI() {
+ public void startTransitionToCredentialUI(boolean isError) {
updateSize(AuthDialog.SIZE_LARGE);
mCallback.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt
index 631511c231e4..68db564606fd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt
@@ -38,7 +38,7 @@ interface AuthBiometricViewAdapter {
fun onHelp(@BiometricAuthenticator.Modality modality: Int, help: String)
- fun startTransitionToCredentialUI()
+ fun startTransitionToCredentialUI(isError: Boolean)
fun requestLayout()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 7f706859abb3..76181094c4b3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -58,6 +58,10 @@ import android.widget.ScrollView;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
+import androidx.core.view.AccessibilityDelegateCompat;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+
import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
@@ -333,6 +337,20 @@ public class AuthContainerView extends LinearLayout
addView(mFrameLayout);
mBiometricScrollView = mFrameLayout.findViewById(R.id.biometric_scrollview);
mBackgroundView = mFrameLayout.findViewById(R.id.background);
+ ViewCompat.setAccessibilityDelegate(mBackgroundView, new AccessibilityDelegateCompat() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host,
+ AccessibilityNodeInfoCompat info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ info.addAction(
+ new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ AccessibilityNodeInfoCompat.ACTION_CLICK,
+ mContext.getString(R.string.biometric_dialog_cancel_authentication)
+ )
+ );
+ }
+ });
+
mPanelView = mFrameLayout.findViewById(R.id.panel);
mPanelController = new AuthPanelController(mContext, mPanelView);
mBackgroundExecutor = bgExecutor;
@@ -374,7 +392,6 @@ public class AuthContainerView extends LinearLayout
if (Utils.isBiometricAllowed(config.mPromptInfo)) {
mPromptSelectorInteractorProvider.get().useBiometricsForAuthentication(
config.mPromptInfo,
- config.mRequireConfirmation,
config.mUserId,
config.mOperationId,
new BiometricModalities(fpProps, faceProps));
@@ -802,9 +819,9 @@ public class AuthContainerView extends LinearLayout
}
@Override
- public void animateToCredentialUI() {
+ public void animateToCredentialUI(boolean isError) {
if (mBiometricView != null) {
- mBiometricView.startTransitionToCredentialUI();
+ mBiometricView.startTransitionToCredentialUI(isError);
} else {
Log.e(TAG, "animateToCredentialUI(): mBiometricView is null");
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 57f1928fe545..2ca4f0cfe3b1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -85,10 +85,11 @@ import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.data.repository.BiometricType;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
+import kotlin.Unit;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -102,7 +103,6 @@ import java.util.Set;
import javax.inject.Inject;
import javax.inject.Provider;
-import kotlin.Unit;
import kotlinx.coroutines.CoroutineScope;
/**
@@ -185,18 +185,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
private final @Background DelayableExecutor mBackgroundExecutor;
private final DisplayInfo mCachedDisplayInfo = new DisplayInfo();
- private final VibratorHelper mVibratorHelper;
-
- private void vibrateSuccess(int modality) {
- mVibratorHelper.vibrateAuthSuccess(
- getClass().getSimpleName() + ", modality = " + modality + "BP::success");
- }
-
- private void vibrateError(int modality) {
- mVibratorHelper.vibrateAuthError(
- getClass().getSimpleName() + ", modality = " + modality + "BP::error");
- }
-
@VisibleForTesting
final TaskStackListener mTaskStackListener = new TaskStackListener() {
@Override
@@ -776,7 +764,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
@NonNull InteractionJankMonitor jankMonitor,
@Main Handler handler,
@Background DelayableExecutor bgExecutor,
- @NonNull VibratorHelper vibrator,
@NonNull UdfpsUtils udfpsUtils) {
mContext = context;
mFeatureFlags = featureFlags;
@@ -798,7 +785,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
mUdfpsEnrolledForUser = new SparseBooleanArray();
mSfpsEnrolledForUser = new SparseBooleanArray();
mFaceEnrolledForUser = new SparseBooleanArray();
- mVibratorHelper = vibrator;
mUdfpsUtils = udfpsUtils;
mApplicationCoroutineScope = applicationCoroutineScope;
@@ -987,8 +973,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
public void onBiometricAuthenticated(@Modality int modality) {
if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: ");
- vibrateSuccess(modality);
-
if (mCurrentDialog != null) {
mCurrentDialog.onAuthenticationSucceeded(modality);
} else {
@@ -1048,6 +1032,18 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
return false;
}
+ private String getNotRecognizedString(@Modality int modality) {
+ final int messageRes;
+ final int userId = mCurrentDialogArgs.argi1;
+ if (isFaceAuthEnrolled(userId) && isFingerprintEnrolled(userId)) {
+ messageRes = modality == TYPE_FACE
+ ? R.string.fingerprint_dialog_use_fingerprint_instead
+ : R.string.fingerprint_error_not_match;
+ } else {
+ messageRes = R.string.biometric_not_recognized;
+ }
+ return mContext.getString(messageRes);
+ }
private String getErrorString(@Modality int modality, int error, int vendorCode) {
switch (modality) {
@@ -1073,8 +1069,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
Log.d(TAG, String.format("onBiometricError(%d, %d, %d)", modality, error, vendorCode));
}
- vibrateError(modality);
-
final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT)
|| (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT);
@@ -1091,10 +1085,11 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
if (mCurrentDialog != null) {
if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) {
if (DEBUG) Log.d(TAG, "onBiometricError, lockout");
- mCurrentDialog.animateToCredentialUI();
+ mCurrentDialog.animateToCredentialUI(true /* isError */);
} else if (isSoftError) {
- final String errorMessage = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED)
- ? mContext.getString(R.string.biometric_not_recognized)
+ final String errorMessage = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED
+ || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT)
+ ? getNotRecognizedString(modality)
: getErrorString(modality, error, vendorCode);
if (DEBUG) Log.d(TAG, "onBiometricError, soft error: " + errorMessage);
// The camera privacy error can return before the prompt initializes its state,
@@ -1204,8 +1199,11 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
final PromptInfo promptInfo = (PromptInfo) args.arg1;
final int[] sensorIds = (int[]) args.arg3;
+
+ // TODO(b/251476085): remove these unused parameters (replaced with SSOT elsewhere)
final boolean credentialAllowed = (boolean) args.arg4;
final boolean requireConfirmation = (boolean) args.arg5;
+
final int userId = args.argi1;
final String opPackageName = (String) args.arg6;
final long operationId = args.argl1;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
index b6eabfa76e36..3cfc6f280110 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
@@ -162,7 +162,7 @@ public interface AuthDialog extends Dumpable {
/**
* Animate to credential UI. Typically called after biometric is locked out.
*/
- void animateToCredentialUI();
+ void animateToCredentialUI(boolean isError);
/**
* @return true if device credential is allowed.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
index 6db266f4f1cb..9d8dcc1efdd8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
@@ -21,6 +21,8 @@ import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FACE_REENROLL_DIALOG;
import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -30,6 +32,9 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.biometrics.BiometricStateListener;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
@@ -42,7 +47,6 @@ import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-
import java.util.Optional;
import javax.inject.Inject;
@@ -69,6 +73,8 @@ public class BiometricNotificationService implements CoreStartable {
private final NotificationManager mNotificationManager;
private final BiometricNotificationBroadcastReceiver mBroadcastReceiver;
private final FingerprintReEnrollNotification mFingerprintReEnrollNotification;
+ private final FingerprintManager mFingerprintManager;
+ private final FaceManager mFaceManager;
private NotificationChannel mNotificationChannel;
private boolean mFaceNotificationQueued;
private boolean mFingerprintNotificationQueued;
@@ -119,14 +125,29 @@ public class BiometricNotificationService implements CoreStartable {
}
};
+ private final BiometricStateListener mFaceStateListener = new BiometricStateListener() {
+ @Override
+ public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
+ mNotificationManager.cancelAsUser(TAG, FACE_NOTIFICATION_ID, UserHandle.CURRENT);
+ }
+ };
+
+ private final BiometricStateListener mFingerprintStateListener = new BiometricStateListener() {
+ @Override
+ public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
+ mNotificationManager.cancelAsUser(TAG, FINGERPRINT_NOTIFICATION_ID, UserHandle.CURRENT);
+ }
+ };
@Inject
- public BiometricNotificationService(Context context,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardStateController keyguardStateController,
- Handler handler, NotificationManager notificationManager,
- BiometricNotificationBroadcastReceiver biometricNotificationBroadcastReceiver,
- Optional<FingerprintReEnrollNotification> fingerprintReEnrollNotification) {
+ public BiometricNotificationService(@NonNull Context context,
+ @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
+ @NonNull KeyguardStateController keyguardStateController,
+ @NonNull Handler handler, @NonNull NotificationManager notificationManager,
+ @NonNull BiometricNotificationBroadcastReceiver biometricNotificationBroadcastReceiver,
+ @NonNull Optional<FingerprintReEnrollNotification> fingerprintReEnrollNotification,
+ @Nullable FingerprintManager fingerprintManager,
+ @Nullable FaceManager faceManager) {
mContext = context;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mKeyguardStateController = keyguardStateController;
@@ -135,6 +156,8 @@ public class BiometricNotificationService implements CoreStartable {
mBroadcastReceiver = biometricNotificationBroadcastReceiver;
mFingerprintReEnrollNotification = fingerprintReEnrollNotification.orElse(
new FingerprintReEnrollNotificationImpl());
+ mFingerprintManager = fingerprintManager;
+ mFaceManager = faceManager;
}
@Override
@@ -148,9 +171,16 @@ public class BiometricNotificationService implements CoreStartable {
intentFilter.addAction(ACTION_SHOW_FACE_REENROLL_DIALOG);
mContext.registerReceiver(mBroadcastReceiver, intentFilter,
Context.RECEIVER_EXPORTED_UNAUDITED);
+ if (mFingerprintManager != null) {
+ mFingerprintManager.registerBiometricStateListener(mFingerprintStateListener);
+ }
+ if (mFaceManager != null) {
+ mFaceManager.registerBiometricStateListener(mFaceStateListener);
+ }
}
private void queueFaceReenrollNotification() {
+ Log.d(TAG, "Face re-enroll notification queued.");
mFaceNotificationQueued = true;
final String title = mContext.getString(R.string.face_re_enroll_notification_title);
final String content = mContext.getString(
@@ -163,6 +193,7 @@ public class BiometricNotificationService implements CoreStartable {
}
private void queueFingerprintReenrollNotification() {
+ Log.d(TAG, "Fingerprint re-enroll notification queued.");
mFingerprintNotificationQueued = true;
final String title = mContext.getString(R.string.fingerprint_re_enroll_notification_title);
final String content = mContext.getString(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
new file mode 100644
index 000000000000..a24a47bb44df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.systemui.biometrics
+
+import android.content.res.Resources
+import android.os.Bundle
+import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo
+import com.android.keyguard.FaceAuthApiRequestReason
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import javax.inject.Inject
+
+/**
+ * Accessibility delegate that will add a click accessibility action to a view when face auth can
+ * run. When the click a11y action is triggered, face auth will retry.
+ */
+@SysUISingleton
+class FaceAuthAccessibilityDelegate
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val faceAuthInteractor: KeyguardFaceAuthInteractor,
+) : View.AccessibilityDelegate() {
+ override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ if (keyguardUpdateMonitor.shouldListenForFace()) {
+ val clickActionToRetryFace =
+ AccessibilityNodeInfo.AccessibilityAction(
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
+ resources.getString(R.string.retry_face)
+ )
+ info.addAction(clickActionToRetryFace)
+ }
+ }
+
+ override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
+ return if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id) {
+ keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.ACCESSIBILITY_ACTION)
+ faceAuthInteractor.onAccessibilityAction()
+ true
+ } else super.performAccessibilityAction(host, action, args)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 10e45dadee60..0ccda1f5fd7c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -188,6 +188,8 @@ public class UdfpsController implements DozeReceiver, Dumpable {
@Nullable private VelocityTracker mVelocityTracker;
// The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active.
private int mActivePointerId = -1;
+ // Whether a pointer has been pilfered for current gesture
+ private boolean mPointerPilfered = false;
// The timestamp of the most recent touch log.
private long mTouchLogTime;
// The timestamp of the most recent log of a touch InteractionEvent.
@@ -354,7 +356,8 @@ public class UdfpsController implements DozeReceiver, Dumpable {
UdfpsController.this.mAlternateTouchProvider.onUiReady();
} else {
final long requestId = (mOverlay != null) ? mOverlay.getRequestId() : 0L;
- UdfpsController.this.mFingerprintManager.onUiReady(requestId, sensorId);
+ UdfpsController.this.mFingerprintManager.onUdfpsUiEvent(
+ FingerprintManager.UDFPS_UI_READY, requestId, sensorId);
}
}
}
@@ -557,6 +560,11 @@ public class UdfpsController implements DozeReceiver, Dumpable {
|| mPrimaryBouncerInteractor.isInTransit()) {
return false;
}
+ if (event.getAction() == MotionEvent.ACTION_DOWN
+ || event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
+ // Reset on ACTION_DOWN, start of new gesture
+ mPointerPilfered = false;
+ }
final TouchProcessorResult result = mTouchProcessor.processTouch(event, mActivePointerId,
mOverlayParams);
@@ -636,10 +644,12 @@ public class UdfpsController implements DozeReceiver, Dumpable {
shouldPilfer = true;
}
- // Execute the pilfer
- if (shouldPilfer) {
+ // Pilfer only once per gesture, don't pilfer for BP
+ if (shouldPilfer && !mPointerPilfered
+ && getBiometricSessionType() != SESSION_BIOMETRIC_PROMPT) {
mInputManager.pilferPointers(
mOverlay.getOverlayView().getViewRootImpl().getInputToken());
+ mPointerPilfered = true;
}
return processedTouch.getTouchData().isWithinBounds(mOverlayParams.getNativeSensorBounds());
@@ -958,6 +968,10 @@ public class UdfpsController implements DozeReceiver, Dumpable {
mOnFingerDown = false;
mAttemptedToDismissKeyguard = false;
mOrientationListener.enable();
+ if (mFingerprintManager != null) {
+ mFingerprintManager.onUdfpsUiEvent(FingerprintManager.UDFPS_UI_OVERLAY_SHOWN,
+ overlay.getRequestId(), mSensorProps.sensorId);
+ }
} else {
Log.v(TAG, "showUdfpsOverlay | the overlay is already showing");
}
@@ -1099,7 +1113,8 @@ public class UdfpsController implements DozeReceiver, Dumpable {
mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
});
} else {
- mFingerprintManager.onUiReady(requestId, mSensorProps.sensorId);
+ mFingerprintManager.onUdfpsUiEvent(FingerprintManager.UDFPS_UI_READY, requestId,
+ mSensorProps.sensorId);
mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
index b538085fa40d..1ca57e77034c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
@@ -60,6 +60,13 @@ object Utils {
return dp * (density / DisplayMetrics.DENSITY_DEFAULT)
}
+ /**
+ * Note: Talkback 14.0 has new rate-limitation design to reduce frequency
+ * of TYPE_WINDOW_CONTENT_CHANGED events to once every 30 seconds.
+ * (context: b/281765653#comment18)
+ * Using {@link View#announceForAccessibility} instead as workaround when sending events
+ * exceeding this frequency is required.
+ */
@JvmStatic
fun notifyAccessibilityContentChanged(am: AccessibilityManager, view: ViewGroup) {
if (!am.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index ddf1457e385c..a5e846ad61ca 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -17,6 +17,8 @@
package com.android.systemui.biometrics.dagger
import com.android.settingslib.udfps.UdfpsUtils
+import com.android.systemui.biometrics.data.repository.FaceSettingsRepository
+import com.android.systemui.biometrics.data.repository.FaceSettingsRepositoryImpl
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepositoryImpl
import com.android.systemui.biometrics.data.repository.PromptRepository
@@ -47,6 +49,10 @@ interface BiometricsModule {
@Binds
@SysUISingleton
+ fun faceSettings(impl: FaceSettingsRepositoryImpl): FaceSettingsRepository
+
+ @Binds
+ @SysUISingleton
fun biometricPromptRepository(impl: PromptRepositoryImpl): PromptRepository
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt
index 20c3e4098e83..f7f910391566 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt
@@ -56,10 +56,10 @@ interface UdfpsModule {
)
)
} else {
- BoundingBoxOverlapDetector()
+ BoundingBoxOverlapDetector(values[2])
}
} else {
- return BoundingBoxOverlapDetector()
+ return BoundingBoxOverlapDetector(1f)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepository.kt
new file mode 100644
index 000000000000..3d5ed823f771
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepository.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.systemui.biometrics.data.repository
+
+import android.os.Handler
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.settings.SecureSettings
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+
+/**
+ * Repository for the global state of users Face Unlock preferences.
+ *
+ * Largely a wrapper around [SecureSettings]'s proxy to Settings.Secure.
+ */
+interface FaceSettingsRepository {
+
+ /** Get Settings for the given user [id]. */
+ fun forUser(id: Int?): FaceUserSettingsRepository
+}
+
+@SysUISingleton
+class FaceSettingsRepositoryImpl
+@Inject
+constructor(
+ @Main private val mainHandler: Handler,
+ private val secureSettings: SecureSettings,
+) : FaceSettingsRepository {
+
+ private val userSettings = ConcurrentHashMap<Int, FaceUserSettingsRepository>()
+
+ override fun forUser(id: Int?): FaceUserSettingsRepository =
+ if (id != null) {
+ userSettings.computeIfAbsent(id) { _ ->
+ FaceUserSettingsRepositoryImpl(id, mainHandler, secureSettings).also { repo ->
+ repo.start()
+ }
+ }
+ } else {
+ FaceUserSettingsRepositoryImpl.Empty
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt
new file mode 100644
index 000000000000..68c4a10fcfad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.systemui.biometrics.data.repository
+
+import android.database.ContentObserver
+import android.os.Handler
+import android.provider.Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.settings.SecureSettings
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.flowOf
+
+/** Settings for a user. */
+interface FaceUserSettingsRepository {
+ /** The user's id. */
+ val userId: Int
+
+ /** If BiometricPrompt should always require confirmation (overrides app's preference). */
+ val alwaysRequireConfirmationInApps: Flow<Boolean>
+}
+
+class FaceUserSettingsRepositoryImpl(
+ override val userId: Int,
+ @Main private val mainHandler: Handler,
+ private val secureSettings: SecureSettings,
+) : FaceUserSettingsRepository {
+
+ /** Indefinitely subscribe to user preference changes. */
+ fun start() {
+ watch(
+ FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
+ _alwaysRequireConfirmationInApps,
+ )
+ }
+
+ private var _alwaysRequireConfirmationInApps = MutableStateFlow(false)
+ override val alwaysRequireConfirmationInApps: Flow<Boolean> =
+ _alwaysRequireConfirmationInApps.asStateFlow()
+
+ /** Defaults to use when no user is specified. */
+ object Empty : FaceUserSettingsRepository {
+ override val userId = -1
+ override val alwaysRequireConfirmationInApps = flowOf(false)
+ }
+
+ private fun watch(
+ key: String,
+ toUpdate: MutableStateFlow<Boolean>,
+ defaultValue: Boolean = false,
+ ) = secureSettings.watch(userId, mainHandler, key, defaultValue) { v -> toUpdate.value = v }
+}
+
+private fun SecureSettings.watch(
+ userId: Int,
+ handler: Handler,
+ key: String,
+ defaultValue: Boolean = false,
+ onChange: (Boolean) -> Unit,
+) {
+ fun fetch(): Boolean = getIntForUser(key, if (defaultValue) 1 else 0, userId) > 0
+
+ registerContentObserverForUser(
+ key,
+ false /* notifyForDescendants */,
+ object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean) = onChange(fetch())
+ },
+ userId
+ )
+
+ onChange(fetch())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
index b4dc272b71da..b35fbbc7bb32 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.systemui.biometrics.data.repository
import android.hardware.biometrics.PromptInfo
@@ -12,6 +28,10 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
/**
* A repository for the global state of BiometricPrompt.
@@ -40,7 +60,7 @@ interface PromptRepository {
*
* Note: overlaps/conflicts with [PromptInfo.isConfirmationRequested], which needs clean up.
*/
- val isConfirmationRequired: StateFlow<Boolean>
+ val isConfirmationRequired: Flow<Boolean>
/** Update the prompt configuration, which should be set before [isShowing]. */
fun setPrompt(
@@ -48,7 +68,6 @@ interface PromptRepository {
userId: Int,
gatekeeperChallenge: Long?,
kind: PromptKind,
- requireConfirmation: Boolean = false,
)
/** Unset the prompt info. */
@@ -56,8 +75,12 @@ interface PromptRepository {
}
@SysUISingleton
-class PromptRepositoryImpl @Inject constructor(private val authController: AuthController) :
- PromptRepository {
+class PromptRepositoryImpl
+@Inject
+constructor(
+ private val faceSettings: FaceSettingsRepository,
+ private val authController: AuthController,
+) : PromptRepository {
override val isShowing: Flow<Boolean> = conflatedCallbackFlow {
val callback =
@@ -85,21 +108,30 @@ class PromptRepositoryImpl @Inject constructor(private val authController: AuthC
private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.Biometric())
override val kind = _kind.asStateFlow()
- private val _isConfirmationRequired: MutableStateFlow<Boolean> = MutableStateFlow(false)
- override val isConfirmationRequired = _isConfirmationRequired.asStateFlow()
+ private val _faceSettings =
+ _userId.map { id -> faceSettings.forUser(id) }.distinctUntilChanged()
+ private val _faceSettingAlwaysRequireConfirmation =
+ _faceSettings.flatMapLatest { it.alwaysRequireConfirmationInApps }.distinctUntilChanged()
+
+ private val _isConfirmationRequired = _promptInfo.map { it?.isConfirmationRequested ?: false }
+ override val isConfirmationRequired =
+ combine(_isConfirmationRequired, _faceSettingAlwaysRequireConfirmation) {
+ appRequiresConfirmation,
+ forceRequireConfirmation ->
+ forceRequireConfirmation || appRequiresConfirmation
+ }
+ .distinctUntilChanged()
override fun setPrompt(
promptInfo: PromptInfo,
userId: Int,
gatekeeperChallenge: Long?,
kind: PromptKind,
- requireConfirmation: Boolean,
) {
_kind.value = kind
_userId.value = userId
_challenge.value = gatekeeperChallenge
_promptInfo.value = promptInfo
- _isConfirmationRequired.value = requireConfirmation
}
override fun unsetPrompt() {
@@ -107,7 +139,6 @@ class PromptRepositoryImpl @Inject constructor(private val authController: AuthC
_userId.value = null
_challenge.value = null
_kind.value = PromptKind.Biometric()
- _isConfirmationRequired.value = false
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index e6e07f9d7794..be99dd92f1bd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -59,13 +59,15 @@ interface PromptSelectorInteractor {
*/
val credentialKind: Flow<PromptKind>
- /** If the API caller requested explicit confirmation after successful authentication. */
- val isConfirmationRequested: Flow<Boolean>
+ /**
+ * If the API caller or the user's personal preferences require explicit confirmation after
+ * successful authentication.
+ */
+ val isConfirmationRequired: Flow<Boolean>
/** Use biometrics for authentication. */
fun useBiometricsForAuthentication(
promptInfo: PromptInfo,
- requireConfirmation: Boolean,
userId: Int,
challenge: Long,
modalities: BiometricModalities,
@@ -114,10 +116,8 @@ constructor(
}
}
- override val isConfirmationRequested: Flow<Boolean> =
- promptRepository.promptInfo
- .map { info -> info?.isConfirmationRequested ?: false }
- .distinctUntilChanged()
+ override val isConfirmationRequired: Flow<Boolean> =
+ promptRepository.isConfirmationRequired.distinctUntilChanged()
override val isCredentialAllowed: Flow<Boolean> =
promptRepository.promptInfo
@@ -142,7 +142,6 @@ constructor(
override fun useBiometricsForAuthentication(
promptInfo: PromptInfo,
- requireConfirmation: Boolean,
userId: Int,
challenge: Long,
modalities: BiometricModalities
@@ -152,7 +151,6 @@ constructor(
userId = userId,
gatekeeperChallenge = challenge,
kind = PromptKind.Biometric(modalities),
- requireConfirmation = requireConfirmation,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModality.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt
index 3197c0935d0b..fb580ca54aff 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModality.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.biometrics.domain.model
+package com.android.systemui.biometrics.shared.model
import android.hardware.biometrics.BiometricAuthenticator
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt
index cf6044f146b0..9b946db0daf0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt
@@ -17,16 +17,30 @@
package com.android.systemui.biometrics.udfps
import android.graphics.Rect
+import android.os.Build
+import android.util.Log
import com.android.systemui.dagger.SysUISingleton
/** Returns whether the touch coordinates are within the sensor's bounding box. */
@SysUISingleton
-class BoundingBoxOverlapDetector : OverlapDetector {
+class BoundingBoxOverlapDetector(private val targetSize: Float) : OverlapDetector {
+
+ private val TAG = "BoundingBoxOverlapDetector"
+
override fun isGoodOverlap(
touchData: NormalizedTouchData,
nativeSensorBounds: Rect,
nativeOverlayBounds: Rect,
- ): Boolean =
- touchData.isWithinBounds(nativeOverlayBounds) &&
- touchData.isWithinBounds(nativeSensorBounds)
+ ): Boolean {
+ val scaledRadius = (nativeSensorBounds.width() / 2) * targetSize
+ val scaledSensorBounds =
+ Rect(
+ (nativeSensorBounds.centerX() - scaledRadius).toInt(),
+ (nativeSensorBounds.centerY() - scaledRadius).toInt(),
+ (nativeSensorBounds.centerX() + scaledRadius).toInt(),
+ (nativeSensorBounds.centerY() + scaledRadius).toInt(),
+ )
+
+ return touchData.isWithinBounds(scaledSensorBounds)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index a881c07021fc..4b610574df8c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.ui.binder
import android.animation.Animator
+import android.annotation.SuppressLint
import android.content.Context
import android.hardware.biometrics.BiometricAuthenticator
import android.hardware.biometrics.BiometricConstants
@@ -25,7 +26,9 @@ import android.hardware.face.FaceManager
import android.os.Bundle
import android.text.method.ScrollingMovementMethod
import android.util.Log
+import android.view.MotionEvent
import android.view.View
+import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
import android.view.accessibility.AccessibilityManager
import android.widget.Button
import android.widget.TextView
@@ -44,11 +47,10 @@ import com.android.systemui.biometrics.AuthBiometricView.Callback
import com.android.systemui.biometrics.AuthBiometricViewAdapter
import com.android.systemui.biometrics.AuthIconController
import com.android.systemui.biometrics.AuthPanelController
-import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.domain.model.BiometricModalities
-import com.android.systemui.biometrics.domain.model.BiometricModality
-import com.android.systemui.biometrics.domain.model.asBiometricModality
+import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.biometrics.shared.model.asBiometricModality
import com.android.systemui.biometrics.ui.BiometricPromptLayout
import com.android.systemui.biometrics.ui.viewmodel.FingerprintStartMode
import com.android.systemui.biometrics.ui.viewmodel.PromptMessage
@@ -68,6 +70,7 @@ private const val TAG = "BiometricViewBinder"
object BiometricViewBinder {
/** Binds a [BiometricPromptLayout] to a [PromptViewModel]. */
+ @SuppressLint("ClickableViewAccessibility")
@JvmStatic
fun bind(
view: BiometricPromptLayout,
@@ -79,9 +82,6 @@ object BiometricViewBinder {
applicationScope: CoroutineScope,
): AuthBiometricViewAdapter {
val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!!
- fun notifyAccessibilityChanged() {
- Utils.notifyAccessibilityContentChanged(accessibilityManager, view)
- }
val textColorError =
view.resources.getColor(R.color.biometric_dialog_error, view.context.theme)
@@ -92,9 +92,11 @@ object BiometricViewBinder {
val subtitleView = view.requireViewById<TextView>(R.id.subtitle)
val descriptionView = view.requireViewById<TextView>(R.id.description)
- // set selected for marquee
- titleView.isSelected = true
- subtitleView.isSelected = true
+ // set selected to enable marquee unless a screen reader is enabled
+ titleView.isSelected =
+ !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
+ subtitleView.isSelected =
+ !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
descriptionView.movementMethod = ScrollingMovementMethod()
val iconViewOverlay = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
@@ -158,7 +160,7 @@ object BiometricViewBinder {
view.updateFingerprintAffordanceSize(iconController)
}
if (iconController is HackyCoexIconController) {
- iconController.faceMode = !viewModel.isConfirmationRequested.first()
+ iconController.faceMode = !viewModel.isConfirmationRequired.first()
}
// the icon controller must be created before this happens for the legacy
@@ -294,17 +296,19 @@ object BiometricViewBinder {
// reuse the icon as a confirm button
launch {
- viewModel.isConfirmButtonVisible
+ viewModel.isIconConfirmButton
.map { isPending ->
when {
isPending && iconController.actsAsConfirmButton ->
- View.OnClickListener { viewModel.confirmAuthenticated() }
+ View.OnTouchListener { _: View, event: MotionEvent ->
+ viewModel.onOverlayTouch(event)
+ }
else -> null
}
}
- .collect { onClick ->
- iconViewOverlay.setOnClickListener(onClick)
- iconView.setOnClickListener(onClick)
+ .collect { onTouch ->
+ iconViewOverlay.setOnTouchListener(onTouch)
+ iconView.setOnTouchListener(onTouch)
}
}
@@ -319,27 +323,40 @@ object BiometricViewBinder {
}
}
- // not sure why this is here, but the legacy code did it probably needed?
- launch {
- viewModel.isAuthenticating.collect { isAuthenticating ->
- if (isAuthenticating) {
- notifyAccessibilityChanged()
- }
- }
- }
-
// dismiss prompt when authenticated and confirmed
launch {
viewModel.isAuthenticated.collect { authState ->
+ // Disable background view for cancelling authentication once authenticated,
+ // and remove from talkback
+ if (authState.isAuthenticated) {
+ // Prevents Talkback from speaking subtitle after already authenticated
+ subtitleView.importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
+ backgroundView.setOnClickListener(null)
+ backgroundView.importantForAccessibility =
+ IMPORTANT_FOR_ACCESSIBILITY_NO
+
+ // Allow icon to be used as confirmation button with a11y enabled
+ if (accessibilityManager.isTouchExplorationEnabled) {
+ iconViewOverlay.setOnClickListener {
+ viewModel.confirmAuthenticated()
+ }
+ iconView.setOnClickListener { viewModel.confirmAuthenticated() }
+ }
+ }
if (authState.isAuthenticatedAndConfirmed) {
view.announceForAccessibility(
view.resources.getString(R.string.biometric_dialog_authenticated)
)
- notifyAccessibilityChanged()
launch {
delay(authState.delay)
- legacyCallback.onAction(Callback.ACTION_AUTHENTICATED)
+ legacyCallback.onAction(
+ if (authState.isAuthenticatedAndExplicitlyConfirmed) {
+ Callback.ACTION_AUTHENTICATED_AND_CONFIRMED
+ } else {
+ Callback.ACTION_AUTHENTICATED
+ }
+ )
}
}
}
@@ -361,7 +378,18 @@ object BiometricViewBinder {
!accessibilityManager.isEnabled ||
!accessibilityManager.isTouchExplorationEnabled
- notifyAccessibilityChanged()
+ /**
+ * Note: Talkback 14.0 has new rate-limitation design to reduce frequency of
+ * TYPE_WINDOW_CONTENT_CHANGED events to once every 30 seconds. (context:
+ * b/281765653#comment18) Using {@link View#announceForAccessibility}
+ * instead as workaround since sending events exceeding this frequency is
+ * required.
+ */
+ indicatorMessageView?.text?.let {
+ if (it.isNotBlank()) {
+ view.announceForAccessibility(it)
+ }
+ }
}
}
}
@@ -390,7 +418,6 @@ private class Spaghetti(
private var lifecycleScope: CoroutineScope? = null
private var modalities: BiometricModalities = BiometricModalities()
- private var faceFailedAtLeastOnce = false
private var legacyCallback: Callback? = null
override var legacyIconController: AuthIconController? = null
@@ -458,7 +485,7 @@ private class Spaghetti(
modalities.hasFaceAndFingerprint &&
(viewModel.fingerprintStartMode.first() != FingerprintStartMode.Pending) &&
(authenticatedModality == BiometricModality.Face) ->
- R.string.biometric_dialog_tap_confirm_with_face
+ R.string.biometric_dialog_tap_confirm_with_face_alt_1
else -> null
}
@@ -470,19 +497,15 @@ private class Spaghetti(
viewModel.ensureFingerprintHasStarted(isDelayed = true)
applicationScope.launch {
- val suppress =
- modalities.hasFaceAndFingerprint &&
- (failedModality == BiometricModality.Face) &&
- faceFailedAtLeastOnce
- if (failedModality == BiometricModality.Face) {
- faceFailedAtLeastOnce = true
- }
-
viewModel.showTemporaryError(
failureReason,
messageAfterError = modalities.asDefaultHelpMessage(applicationContext),
authenticateAfterError = modalities.hasFingerprint,
- suppressIfErrorShowing = suppress,
+ suppressIf = { currentMessage ->
+ modalities.hasFaceAndFingerprint &&
+ failedModality == BiometricModality.Face &&
+ currentMessage.isError
+ },
failedModality = failedModality,
)
}
@@ -495,11 +518,10 @@ private class Spaghetti(
}
applicationScope.launch {
- val suppress =
- modalities.hasFaceAndFingerprint && (errorModality == BiometricModality.Face)
viewModel.showTemporaryError(
error,
- suppressIfErrorShowing = suppress,
+ messageAfterError = modalities.asDefaultHelpMessage(applicationContext),
+ authenticateAfterError = modalities.hasFingerprint,
)
delay(BiometricPrompt.HIDE_DIALOG_DELAY.toLong())
legacyCallback?.onAction(Callback.ACTION_ERROR)
@@ -512,9 +534,12 @@ private class Spaghetti(
}
applicationScope.launch {
- viewModel.showTemporaryHelp(
+ // help messages from the HAL should be displayed as temporary (i.e. soft) errors
+ viewModel.showTemporaryError(
help,
- messageAfterHelp = modalities.asDefaultHelpMessage(applicationContext),
+ messageAfterError = modalities.asDefaultHelpMessage(applicationContext),
+ authenticateAfterError = modalities.hasFingerprint,
+ hapticFeedback = false,
)
}
}
@@ -527,7 +552,7 @@ private class Spaghetti(
else -> false
}
- override fun startTransitionToCredentialUI() {
+ override fun startTransitionToCredentialUI(isError: Boolean) {
applicationScope.launch {
viewModel.onSwitchToCredential()
legacyCallback?.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 2b06f3aa4df3..370b36bcaec2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -28,6 +28,7 @@ import android.view.accessibility.AccessibilityManager
import android.widget.TextView
import androidx.core.animation.addListener
import androidx.core.view.doOnLayout
+import androidx.core.view.isGone
import androidx.lifecycle.lifecycleScope
import com.android.systemui.R
import com.android.systemui.biometrics.AuthDialog
@@ -78,9 +79,11 @@ object BiometricViewSizeBinder {
// cache the original position of the icon view (as done in legacy view)
// this must happen before any size changes can be made
- var iconHolderOriginalY = 0f
view.doOnLayout {
- iconHolderOriginalY = iconHolderView.y
+ // TODO(b/251476085): this old way of positioning has proven itself unreliable
+ // remove this and associated thing like (UdfpsDialogMeasureAdapter) and
+ // pin to the physical sensor
+ val iconHolderOriginalY = iconHolderView.y
// bind to prompt
// TODO(b/251476085): migrate the legacy panel controller and simplify this
@@ -141,7 +144,11 @@ object BiometricViewSizeBinder {
listOf(
iconHolderView.asVerticalAnimator(
duration = duration.toLong(),
- toY = iconHolderOriginalY,
+ toY =
+ iconHolderOriginalY -
+ viewsToHideWhenSmall
+ .filter { it.isGone }
+ .sumOf { it.height },
),
viewsToFadeInOnSizeChange.asFadeInAnimator(
duration = duration.toLong(),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt
index 9cb91b3d51a7..2f9557f70a32 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt
@@ -16,7 +16,7 @@
package com.android.systemui.biometrics.ui.viewmodel
-import com.android.systemui.biometrics.domain.model.BiometricModality
+import com.android.systemui.biometrics.shared.model.BiometricModality
/**
* The authenticated state with the [authenticatedModality] (when [isAuthenticated]) with an
@@ -29,10 +29,16 @@ data class PromptAuthState(
val needsUserConfirmation: Boolean = false,
val delay: Long = 0,
) {
+ private var wasConfirmed = false
+
/** If authentication was successful and the user has confirmed (or does not need to). */
val isAuthenticatedAndConfirmed: Boolean
get() = isAuthenticated && !needsUserConfirmation
+ /** Same as [isAuthenticatedAndConfirmed] but only true if the user clicked a confirm button. */
+ val isAuthenticatedAndExplicitlyConfirmed: Boolean
+ get() = isAuthenticated && wasConfirmed
+
/** If a successful authentication has not occurred. */
val isNotAuthenticated: Boolean
get() = !isAuthenticated
@@ -45,12 +51,16 @@ data class PromptAuthState(
val isAuthenticatedByFingerprint: Boolean
get() = isAuthenticated && authenticatedModality == BiometricModality.Fingerprint
- /** Copies this state, but toggles [needsUserConfirmation] to false. */
- fun asConfirmed(): PromptAuthState =
+ /**
+ * Copies this state, but toggles [needsUserConfirmation] to false and ensures that
+ * [isAuthenticatedAndExplicitlyConfirmed] is true.
+ */
+ fun asExplicitlyConfirmed(): PromptAuthState =
PromptAuthState(
- isAuthenticated = isAuthenticated,
- authenticatedModality = authenticatedModality,
- needsUserConfirmation = false,
- delay = delay,
- )
+ isAuthenticated = isAuthenticated,
+ authenticatedModality = authenticatedModality,
+ needsUserConfirmation = false,
+ delay = delay,
+ )
+ .apply { wasConfirmed = true }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt
index 219da716f7d9..50f491142949 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt
@@ -33,9 +33,9 @@ sealed interface PromptMessage {
else -> ""
}
- /** If this is an [Error] or [Help] message. */
- val isErrorOrHelp: Boolean
- get() = this is Error || this is Help
+ /** If this is an [Error]. */
+ val isError: Boolean
+ get() = this is Error
/** An error message. */
data class Error(val errorMessage: String) : PromptMessage
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 2f8ed096f4ba..a148d087eb3b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -17,11 +17,13 @@ package com.android.systemui.biometrics.ui.viewmodel
import android.hardware.biometrics.BiometricPrompt
import android.util.Log
+import android.view.MotionEvent
import com.android.systemui.biometrics.AuthBiometricView
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.model.BiometricModalities
-import com.android.systemui.biometrics.domain.model.BiometricModality
+import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
@@ -41,6 +43,7 @@ class PromptViewModel
@Inject
constructor(
private val interactor: PromptSelectorInteractor,
+ private val vibrator: VibratorHelper,
) {
/** The set of modalities available for this prompt */
val modalities: Flow<BiometricModalities> =
@@ -61,8 +64,18 @@ constructor(
/** If the user has successfully authenticated and confirmed (when explicitly required). */
val isAuthenticated: Flow<PromptAuthState> = _isAuthenticated.asStateFlow()
- /** If the API caller requested explicit confirmation after successful authentication. */
- val isConfirmationRequested: Flow<Boolean> = interactor.isConfirmationRequested
+ private val _isOverlayTouched: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ /**
+ * If the API caller or the user's personal preferences require explicit confirmation after
+ * successful authentication.
+ */
+ val isConfirmationRequired: Flow<Boolean> =
+ combine(_isOverlayTouched, interactor.isConfirmationRequired) {
+ isOverlayTouched,
+ isConfirmationRequired ->
+ !isOverlayTouched && isConfirmationRequired
+ }
/** The kind of credential the user has. */
val credentialKind: Flow<PromptKind> = interactor.credentialKind
@@ -91,7 +104,7 @@ constructor(
_forceLargeSize,
_forceMediumSize,
modalities,
- interactor.isConfirmationRequested,
+ interactor.isConfirmationRequired,
fingerprintStartMode,
) { forceLarge, forceMedium, modalities, confirmationRequired, fpStartMode ->
when {
@@ -136,6 +149,12 @@ constructor(
}
.distinctUntilChanged()
+ /** If the icon can be used as a confirmation button. */
+ val isIconConfirmButton: Flow<Boolean> =
+ combine(size, interactor.isConfirmationRequired) { size, isConfirmationRequired ->
+ size.isNotSmall && isConfirmationRequired
+ }
+
/** If the negative button should be shown. */
val isNegativeButtonVisible: Flow<Boolean> =
combine(
@@ -202,37 +221,43 @@ constructor(
private var messageJob: Job? = null
/**
- * Show a temporary error [message] associated with an optional [failedModality].
+ * Show a temporary error [message] associated with an optional [failedModality] and play
+ * [hapticFeedback].
*
- * An optional [messageAfterError] will be shown via [showAuthenticating] when
- * [authenticateAfterError] is set (or via [showHelp] when not set) after the error is
- * dismissed.
+ * The [messageAfterError] will be shown via [showAuthenticating] when [authenticateAfterError]
+ * is set (or via [showHelp] when not set) after the error is dismissed.
*
- * The error is ignored if the user has already authenticated and it is treated as
- * [onSilentError] if [suppressIfErrorShowing] is set and an error message is already showing.
+ * The error is ignored if the user has already authenticated or if [suppressIf] is true given
+ * the currently showing [PromptMessage].
*/
suspend fun showTemporaryError(
message: String,
- messageAfterError: String = "",
- authenticateAfterError: Boolean = false,
- suppressIfErrorShowing: Boolean = false,
+ messageAfterError: String,
+ authenticateAfterError: Boolean,
+ suppressIf: (PromptMessage) -> Boolean = { false },
+ hapticFeedback: Boolean = true,
failedModality: BiometricModality = BiometricModality.None,
) = coroutineScope {
if (_isAuthenticated.value.isAuthenticated) {
return@coroutineScope
}
- if (_message.value.isErrorOrHelp && suppressIfErrorShowing) {
- onSilentError(failedModality)
+
+ _canTryAgainNow.value = supportsRetry(failedModality)
+
+ if (suppressIf(_message.value)) {
return@coroutineScope
}
_isAuthenticating.value = false
_isAuthenticated.value = PromptAuthState(false)
_forceMediumSize.value = true
- _canTryAgainNow.value = supportsRetry(failedModality)
_message.value = PromptMessage.Error(message)
_legacyState.value = AuthBiometricView.STATE_ERROR
+ if (hapticFeedback) {
+ vibrator.error(failedModality)
+ }
+
messageJob?.cancel()
messageJob = launch {
delay(BiometricPrompt.HIDE_DIALOG_DELAY.toLong())
@@ -245,18 +270,6 @@ constructor(
}
/**
- * Call instead of [showTemporaryError] if an error from the HAL should be silently ignored to
- * enable retry (if the [failedModality] supports retrying).
- *
- * Ignored if the user has already authenticated.
- */
- private fun onSilentError(failedModality: BiometricModality = BiometricModality.None) {
- if (_isAuthenticated.value.isNotAuthenticated) {
- _canTryAgainNow.value = supportsRetry(failedModality)
- }
- }
-
- /**
* Call to ensure the fingerprint sensor has started. Either when the dialog is first shown
* (most cases) or when it should be enabled after a first error (coex implicit flow).
*/
@@ -287,8 +300,10 @@ constructor(
if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty
_forceMediumSize.value = true
_legacyState.value =
- if (alreadyAuthenticated) {
+ if (alreadyAuthenticated && isConfirmationRequired.first()) {
AuthBiometricView.STATE_PENDING_CONFIRMATION
+ } else if (alreadyAuthenticated && !isConfirmationRequired.first()) {
+ AuthBiometricView.STATE_AUTHENTICATED
} else {
AuthBiometricView.STATE_HELP
}
@@ -373,6 +388,10 @@ constructor(
AuthBiometricView.STATE_AUTHENTICATED
}
+ if (!needsUserConfirmation) {
+ vibrator.success(modality)
+ }
+
messageJob?.cancel()
messageJob = null
@@ -382,19 +401,11 @@ constructor(
}
private suspend fun needsExplicitConfirmation(modality: BiometricModality): Boolean {
- val availableModalities = modalities.first()
- val confirmationRequested = interactor.isConfirmationRequested.first()
-
- if (availableModalities.hasFaceAndFingerprint) {
- // coex only needs confirmation when face is successful, unless it happens on the
- // first attempt (i.e. without failure) before fingerprint scanning starts
- if (modality == BiometricModality.Face) {
- return (fingerprintStartMode.first() != FingerprintStartMode.Pending) ||
- confirmationRequested
- }
- }
- if (availableModalities.hasFaceOnly) {
- return confirmationRequested
+ val confirmationRequired = isConfirmationRequired.first()
+
+ // Only worry about confirmationRequired if face was used to unlock
+ if (modality == BiometricModality.Face) {
+ return confirmationRequired
}
// fingerprint only never requires confirmation
return false
@@ -409,20 +420,41 @@ constructor(
fun confirmAuthenticated() {
val authState = _isAuthenticated.value
if (authState.isNotAuthenticated) {
- "Cannot show authenticated after authenticated"
Log.w(TAG, "Cannot confirm authenticated when not authenticated")
return
}
- _isAuthenticated.value = authState.asConfirmed()
+ _isAuthenticated.value = authState.asExplicitlyConfirmed()
_message.value = PromptMessage.Empty
_legacyState.value = AuthBiometricView.STATE_AUTHENTICATED
+ vibrator.success(authState.authenticatedModality)
+
messageJob?.cancel()
messageJob = null
}
/**
+ * Touch event occurred on the overlay
+ *
+ * Tracks whether a finger is currently down to set [_isOverlayTouched] to be used as user
+ * confirmation
+ */
+ fun onOverlayTouch(event: MotionEvent): Boolean {
+ if (event.actionMasked == MotionEvent.ACTION_DOWN) {
+ _isOverlayTouched.value = true
+
+ if (_isAuthenticated.value.needsUserConfirmation) {
+ confirmAuthenticated()
+ }
+ return true
+ } else if (event.actionMasked == MotionEvent.ACTION_UP) {
+ _isOverlayTouched.value = false
+ }
+ return false
+ }
+
+ /**
* Switch to the credential view.
*
* TODO(b/251476085): this should be decoupled from the shared panel controller
@@ -431,6 +463,12 @@ constructor(
_forceLargeSize.value = true
}
+ private fun VibratorHelper.success(modality: BiometricModality) =
+ vibrateAuthSuccess("$TAG, modality = $modality BP::success")
+
+ private fun VibratorHelper.error(modality: BiometricModality = BiometricModality.None) =
+ vibrateAuthError("$TAG, modality = $modality BP::error")
+
companion object {
private const val TAG = "PromptViewModel"
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
index 2dd98dcf41b6..323070a84863 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
@@ -22,6 +22,7 @@ import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
+import com.android.systemui.shade.TouchLogger
import kotlin.math.pow
import kotlin.math.sqrt
import kotlinx.coroutines.DisposableHandle
@@ -83,6 +84,10 @@ class LongPressHandlingView(
interactionHandler.isLongPressHandlingEnabled = isEnabled
}
+ override fun dispatchTouchEvent(event: MotionEvent): Boolean {
+ return TouchLogger.logDispatchTouch("long_press", event, super.dispatchTouchEvent(event))
+ }
+
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
return interactionHandler.onTouchEvent(event?.toModel())
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
index a431a59fcef6..a90980fddfb0 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
@@ -16,6 +16,7 @@
package com.android.systemui.dagger;
+import com.android.systemui.globalactions.ShutdownUiModule;
import com.android.systemui.keyguard.CustomizationProvider;
import com.android.systemui.statusbar.NotificationInsetsModule;
import com.android.systemui.statusbar.QsFrameTranslateModule;
@@ -31,6 +32,7 @@ import dagger.Subcomponent;
DependencyProvider.class,
NotificationInsetsModule.class,
QsFrameTranslateModule.class,
+ ShutdownUiModule.class,
SystemUIBinder.class,
SystemUIModule.class,
SystemUICoreStartableModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index 4e62104034ee..ac0d3c8f3d36 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -34,6 +34,7 @@ import com.android.systemui.FaceScanningOverlay
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
import java.util.concurrent.Executor
@@ -47,6 +48,7 @@ class FaceScanningProviderFactory @Inject constructor(
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@Main private val mainExecutor: Executor,
private val logger: ScreenDecorationsLogger,
+ private val featureFlags: FeatureFlags,
) : DecorProviderFactory() {
private val display = context.display
private val displayInfo = DisplayInfo()
@@ -86,6 +88,7 @@ class FaceScanningProviderFactory @Inject constructor(
keyguardUpdateMonitor,
mainExecutor,
logger,
+ featureFlags,
)
)
}
@@ -110,6 +113,7 @@ class FaceScanningOverlayProviderImpl(
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val mainExecutor: Executor,
private val logger: ScreenDecorationsLogger,
+ private val featureFlags: FeatureFlags,
) : BoundDecorProvider() {
override val viewId: Int = com.android.systemui.R.id.face_scanning_anim
@@ -144,6 +148,7 @@ class FaceScanningOverlayProviderImpl(
mainExecutor,
logger,
authController,
+ featureFlags
)
view.id = viewId
view.setColor(tintColor)
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 366056af7ab3..ee8fae599d41 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -221,7 +221,7 @@ object Flags {
// TODO(b/267722622): Tracking Bug
@JvmField
val WALLPAPER_PICKER_UI_FOR_AIWP =
- unreleasedFlag(
+ releasedFlag(
229,
"wallpaper_picker_ui_for_aiwp"
)
@@ -268,6 +268,17 @@ object Flags {
@JvmField
val KEYGUARD_TALKBACK_FIX = releasedFlag(238, "keyguard_talkback_fix")
+ /** Stop running face auth when the display state changes to OFF. */
+ // TODO(b/294221702): Tracking bug.
+ @JvmField val STOP_FACE_AUTH_ON_DISPLAY_OFF = resourceBooleanFlag(245,
+ R.bool.flag_stop_face_auth_on_display_off, "stop_face_auth_on_display_off")
+
+ /** Flag to disable the face scanning animation pulsing. */
+ // TODO(b/295245791): Tracking bug.
+ @JvmField val STOP_PULSING_FACE_SCANNING_ANIMATION = resourceBooleanFlag(246,
+ R.bool.flag_stop_pulsing_face_scanning_animation,
+ "stop_pulsing_face_scanning_animation")
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -662,7 +673,7 @@ object Flags {
@JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag(2200, "udfps_new_touch_detection")
@JvmField val UDFPS_ELLIPSE_DETECTION = releasedFlag(2201, "udfps_ellipse_detection")
// TODO(b/278622168): Tracking Bug
- @JvmField val BIOMETRIC_BP_STRONG = unreleasedFlag(2202, "biometric_bp_strong")
+ @JvmField val BIOMETRIC_BP_STRONG = releasedFlag(2202, "biometric_bp_strong")
// 2300 - stylus
@JvmField val TRACK_STYLUS_EVER_USED = releasedFlag(2300, "track_stylus_ever_used")
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index 290bf0d0734c..c5027cc511a4 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -15,27 +15,12 @@
package com.android.systemui.globalactions;
import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import android.annotation.Nullable;
-import android.annotation.StringRes;
-import android.app.Dialog;
import android.content.Context;
-import android.os.PowerManager;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-import com.android.internal.R;
-import com.android.settingslib.Utils;
import com.android.systemui.plugins.GlobalActions;
-import com.android.systemui.scrim.ScrimDrawable;
import com.android.systemui.statusbar.BlurUtils;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -50,12 +35,14 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks
private final CommandQueue mCommandQueue;
private final GlobalActionsDialogLite mGlobalActionsDialog;
private boolean mDisabled;
+ private ShutdownUi mShutdownUi;
@Inject
public GlobalActionsImpl(Context context, CommandQueue commandQueue,
GlobalActionsDialogLite globalActionsDialog, BlurUtils blurUtils,
KeyguardStateController keyguardStateController,
- DeviceProvisionedController deviceProvisionedController) {
+ DeviceProvisionedController deviceProvisionedController,
+ ShutdownUi shutdownUi) {
mContext = context;
mGlobalActionsDialog = globalActionsDialog;
mKeyguardStateController = keyguardStateController;
@@ -63,6 +50,7 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks
mCommandQueue = commandQueue;
mBlurUtils = blurUtils;
mCommandQueue.addCallback(this);
+ mShutdownUi = shutdownUi;
}
@Override
@@ -80,103 +68,8 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks
@Override
public void showShutdownUi(boolean isReboot, String reason) {
- ScrimDrawable background = new ScrimDrawable();
-
- final Dialog d = new Dialog(mContext,
- com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
-
- d.setOnShowListener(dialog -> {
- if (mBlurUtils.supportsBlursOnWindows()) {
- int backgroundAlpha = (int) (ScrimController.BUSY_SCRIM_ALPHA * 255);
- background.setAlpha(backgroundAlpha);
- mBlurUtils.applyBlur(d.getWindow().getDecorView().getViewRootImpl(),
- (int) mBlurUtils.blurRadiusOfRatio(1), backgroundAlpha == 255);
- } else {
- float backgroundAlpha = mContext.getResources().getFloat(
- com.android.systemui.R.dimen.shutdown_scrim_behind_alpha);
- background.setAlpha((int) (backgroundAlpha * 255));
- }
- });
-
- // Window initialization
- Window window = d.getWindow();
- window.requestFeature(Window.FEATURE_NO_TITLE);
- window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
- // Inflate the decor view, so the attributes below are not overwritten by the theme.
- window.getDecorView();
- window.getAttributes().width = ViewGroup.LayoutParams.MATCH_PARENT;
- window.getAttributes().height = ViewGroup.LayoutParams.MATCH_PARENT;
- window.getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
- window.getAttributes().setFitInsetsTypes(0 /* types */);
- window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
- window.addFlags(
- WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
- | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
- | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
- window.setBackgroundDrawable(background);
- window.setWindowAnimations(com.android.systemui.R.style.Animation_ShutdownUi);
-
- d.setContentView(R.layout.shutdown_dialog);
- d.setCancelable(false);
-
- int color;
- if (mBlurUtils.supportsBlursOnWindows()) {
- color = Utils.getColorAttrDefaultColor(mContext,
- com.android.systemui.R.attr.wallpaperTextColor);
- } else {
- color = mContext.getResources().getColor(
- com.android.systemui.R.color.global_actions_shutdown_ui_text);
- }
-
- ProgressBar bar = d.findViewById(R.id.progress);
- bar.getIndeterminateDrawable().setTint(color);
-
- TextView reasonView = d.findViewById(R.id.text1);
- TextView messageView = d.findViewById(R.id.text2);
-
- reasonView.setTextColor(color);
- messageView.setTextColor(color);
-
- messageView.setText(getRebootMessage(isReboot, reason));
- String rebootReasonMessage = getReasonMessage(reason);
- if (rebootReasonMessage != null) {
- reasonView.setVisibility(View.VISIBLE);
- reasonView.setText(rebootReasonMessage);
- }
-
- d.show();
- }
-
- @StringRes
- private int getRebootMessage(boolean isReboot, @Nullable String reason) {
- if (reason != null && reason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
- return R.string.reboot_to_update_reboot;
- } else if (reason != null && reason.equals(PowerManager.REBOOT_RECOVERY)) {
- return R.string.reboot_to_reset_message;
- } else if (isReboot) {
- return R.string.reboot_to_reset_message;
- } else {
- return R.string.shutdown_progress;
- }
+ mShutdownUi.showShutdownUi(isReboot, reason);
}
-
- @Nullable
- private String getReasonMessage(@Nullable String reason) {
- if (reason != null && reason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
- return mContext.getString(R.string.reboot_to_update_title);
- } else if (reason != null && reason.equals(PowerManager.REBOOT_RECOVERY)) {
- return mContext.getString(R.string.reboot_to_reset_title);
- } else {
- return null;
- }
- }
-
@Override
public void disable(int displayId, int state1, int state2, boolean animate) {
final boolean disabled = (state2 & DISABLE2_GLOBAL_ACTIONS) != 0;
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java
new file mode 100644
index 000000000000..68dc1b3dc7d7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java
@@ -0,0 +1,164 @@
+/*
+ * 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.systemui.globalactions;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.app.Dialog;
+import android.content.Context;
+import android.os.PowerManager;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.R;
+import com.android.settingslib.Utils;
+import com.android.systemui.scrim.ScrimDrawable;
+import com.android.systemui.statusbar.BlurUtils;
+import com.android.systemui.statusbar.phone.ScrimController;
+
+/**
+ * Provides the UI shown during system shutdown.
+ */
+public class ShutdownUi {
+
+ private Context mContext;
+ private BlurUtils mBlurUtils;
+ public ShutdownUi(Context context, BlurUtils blurUtils) {
+ mContext = context;
+ mBlurUtils = blurUtils;
+ }
+
+ /**
+ * Display the shutdown UI.
+ * @param isReboot Whether the device will be rebooting after this shutdown.
+ * @param reason Cause for the shutdown.
+ */
+ public void showShutdownUi(boolean isReboot, String reason) {
+ ScrimDrawable background = new ScrimDrawable();
+
+ final Dialog d = new Dialog(mContext,
+ com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
+
+ d.setOnShowListener(dialog -> {
+ if (mBlurUtils.supportsBlursOnWindows()) {
+ int backgroundAlpha = (int) (ScrimController.BUSY_SCRIM_ALPHA * 255);
+ background.setAlpha(backgroundAlpha);
+ mBlurUtils.applyBlur(d.getWindow().getDecorView().getViewRootImpl(),
+ (int) mBlurUtils.blurRadiusOfRatio(1), backgroundAlpha == 255);
+ } else {
+ float backgroundAlpha = mContext.getResources().getFloat(
+ com.android.systemui.R.dimen.shutdown_scrim_behind_alpha);
+ background.setAlpha((int) (backgroundAlpha * 255));
+ }
+ });
+
+ // Window initialization
+ Window window = d.getWindow();
+ window.requestFeature(Window.FEATURE_NO_TITLE);
+ window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+ // Inflate the decor view, so the attributes below are not overwritten by the theme.
+ window.getDecorView();
+ window.getAttributes().width = ViewGroup.LayoutParams.MATCH_PARENT;
+ window.getAttributes().height = ViewGroup.LayoutParams.MATCH_PARENT;
+ window.getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+ window.getAttributes().setFitInsetsTypes(0 /* types */);
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+ window.addFlags(
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ window.setBackgroundDrawable(background);
+ window.setWindowAnimations(com.android.systemui.R.style.Animation_ShutdownUi);
+
+ d.setContentView(getShutdownDialogContent(isReboot));
+ d.setCancelable(false);
+
+ int color;
+ if (mBlurUtils.supportsBlursOnWindows()) {
+ color = Utils.getColorAttrDefaultColor(mContext,
+ com.android.systemui.R.attr.wallpaperTextColor);
+ } else {
+ color = mContext.getResources().getColor(
+ com.android.systemui.R.color.global_actions_shutdown_ui_text);
+ }
+
+ ProgressBar bar = d.findViewById(R.id.progress);
+ bar.getIndeterminateDrawable().setTint(color);
+
+ TextView reasonView = d.findViewById(R.id.text1);
+ TextView messageView = d.findViewById(R.id.text2);
+
+ reasonView.setTextColor(color);
+ messageView.setTextColor(color);
+
+ messageView.setText(getRebootMessage(isReboot, reason));
+ String rebootReasonMessage = getReasonMessage(reason);
+ if (rebootReasonMessage != null) {
+ reasonView.setVisibility(View.VISIBLE);
+ reasonView.setText(rebootReasonMessage);
+ }
+
+ d.show();
+ }
+
+ /**
+ * Returns the layout resource to use for UI while shutting down.
+ * @param isReboot Whether this is a reboot or a shutdown.
+ * @return
+ */
+ public int getShutdownDialogContent(boolean isReboot) {
+ return R.layout.shutdown_dialog;
+ }
+
+ @StringRes
+ @VisibleForTesting int getRebootMessage(boolean isReboot, @Nullable String reason) {
+ if (reason != null && reason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
+ return R.string.reboot_to_update_reboot;
+ } else if (reason != null && reason.equals(PowerManager.REBOOT_RECOVERY)) {
+ return R.string.reboot_to_reset_message;
+ } else if (isReboot) {
+ return R.string.reboot_to_reset_message;
+ } else {
+ return R.string.shutdown_progress;
+ }
+ }
+
+ @Nullable
+ @VisibleForTesting String getReasonMessage(@Nullable String reason) {
+ if (reason != null && reason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
+ return mContext.getString(R.string.reboot_to_update_title);
+ } else if (reason != null && reason.equals(PowerManager.REBOOT_RECOVERY)) {
+ return mContext.getString(R.string.reboot_to_reset_title);
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUiModule.kt b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUiModule.kt
new file mode 100644
index 000000000000..b7285da49bb7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUiModule.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.systemui.globalactions
+
+import android.content.Context
+import com.android.systemui.statusbar.BlurUtils
+import dagger.Module
+import dagger.Provides
+
+/** Provides the UI shown during system shutdown. */
+@Module
+class ShutdownUiModule {
+ /** Shutdown UI provider. */
+ @Provides
+ fun provideShutdownUi(context: Context?, blurUtils: BlurUtils?): ShutdownUi {
+ return ShutdownUi(context, blurUtils)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index ff390348ff25..7698af4847b9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -932,28 +932,41 @@ class KeyguardUnlockAnimationController @Inject constructor(
/**
* Called by [KeyguardViewMediator] to let us know that the remote animation has finished, and
- * we should clean up all of our state.
+ * we should clean up all of our state. [showKeyguard] will tell us which surface should be
+ * visible after the animation has been completed or canceled.
*
* This is generally triggered by us, calling
* [KeyguardViewMediator.finishSurfaceBehindRemoteAnimation].
*/
- fun notifyFinishedKeyguardExitAnimation(cancelled: Boolean) {
+ fun notifyFinishedKeyguardExitAnimation(showKeyguard: Boolean) {
// Cancel any pending actions.
handler.removeCallbacksAndMessages(null)
- // Make sure we made the surface behind fully visible, just in case. It should already be
- // fully visible. The exit animation is finished, and we should not hold the leash anymore,
- // so forcing it to 1f.
- surfaceBehindAlpha = 1f
- setSurfaceBehindAppearAmount(1f)
+ // The lockscreen surface is gone, so it is now safe to re-show the smartspace.
+ if (lockscreenSmartspace?.visibility == View.INVISIBLE) {
+ lockscreenSmartspace?.visibility = View.VISIBLE
+ }
+
+ if (!showKeyguard) {
+ // Make sure we made the surface behind fully visible, just in case. It should already be
+ // fully visible. The exit animation is finished, and we should not hold the leash anymore,
+ // so forcing it to 1f.
+ surfaceBehindAlpha = 1f
+ setSurfaceBehindAppearAmount(1f)
+
+ try {
+ launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Remote exception in notifyFinishedKeyguardExitAnimation", e)
+ }
+ }
+
+ listeners.forEach { it.onUnlockAnimationFinished() }
+
+ // Reset all state
surfaceBehindAlphaAnimator.cancel()
surfaceBehindEntryAnimator.cancel()
wallpaperCannedUnlockAnimator.cancel()
- try {
- launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */)
- } catch (e: RemoteException) {
- Log.e(TAG, "Remote exception in notifyFinishedKeyguardExitAnimation", e)
- }
// That target is no longer valid since the animation finished, null it out.
surfaceBehindRemoteAnimationTargets = null
@@ -963,13 +976,6 @@ class KeyguardUnlockAnimationController @Inject constructor(
dismissAmountThresholdsReached = false
willUnlockWithInWindowLauncherAnimations = false
willUnlockWithSmartspaceTransition = false
-
- // The lockscreen surface is gone, so it is now safe to re-show the smartspace.
- if (lockscreenSmartspace?.visibility == View.INVISIBLE) {
- lockscreenSmartspace?.visibility = View.VISIBLE
- }
-
- listeners.forEach { it.onUnlockAnimationFinished() }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index cba717038871..5c4e73becbd4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2532,7 +2532,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
} else if (mSurfaceBehindRemoteAnimationRunning) {
// We're already running the keyguard exit animation, likely due to an in-progress swipe
// to unlock.
- exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */);
+ exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* showKeyguard */);
} else if (!mHideAnimationRun) {
if (DEBUG) Log.d(TAG, "tryKeyguardDone: starting pre-hide animation");
mHideAnimationRun = true;
@@ -2989,7 +2989,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mContext.getMainExecutor().execute(() -> {
if (finishedCallback == null) {
mKeyguardUnlockAnimationControllerLazy.get()
- .notifyFinishedKeyguardExitAnimation(false /* cancelled */);
+ .notifyFinishedKeyguardExitAnimation(false /* showKeyguard */);
mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
return;
}
@@ -3106,7 +3106,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// A lock is pending, meaning the keyguard exit animation was cancelled because we're
// re-locking. We should just end the surface-behind animation without exiting the
// keyguard. The pending lock will be handled by onFinishedGoingToSleep().
- finishSurfaceBehindRemoteAnimation(true);
+ finishSurfaceBehindRemoteAnimation(true /* showKeyguard */);
maybeHandlePendingLock();
} else {
Log.d(TAG, "#handleCancelKeyguardExitAnimation: keyguard exit animation cancelled. "
@@ -3115,7 +3115,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// No lock is pending, so the animation was cancelled during the unlock sequence, but
// we should end up unlocked. Show the surface and exit the keyguard.
showSurfaceBehindKeyguard();
- exitKeyguardAndFinishSurfaceBehindRemoteAnimation(true /* cancelled */);
+ exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* showKeyguard */);
}
}
@@ -3126,12 +3126,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
* with the RemoteAnimation, actually hide the keyguard, and clean up state related to the
* keyguard exit animation.
*
- * @param cancelled {@code true} if the animation was cancelled before it finishes.
+ * @param showKeyguard {@code true} if the animation was cancelled and keyguard should remain
+ * visible
*/
- public void exitKeyguardAndFinishSurfaceBehindRemoteAnimation(boolean cancelled) {
- Log.d(TAG, "onKeyguardExitRemoteAnimationFinished");
+ public void exitKeyguardAndFinishSurfaceBehindRemoteAnimation(boolean showKeyguard) {
+ Log.d(TAG, "exitKeyguardAndFinishSurfaceBehindRemoteAnimation");
if (!mSurfaceBehindRemoteAnimationRunning && !mSurfaceBehindRemoteAnimationRequested) {
- Log.d(TAG, "skip onKeyguardExitRemoteAnimationFinished cancelled=" + cancelled
+ Log.d(TAG, "skip onKeyguardExitRemoteAnimationFinished showKeyguard=" + showKeyguard
+ " surfaceAnimationRunning=" + mSurfaceBehindRemoteAnimationRunning
+ " surfaceAnimationRequested=" + mSurfaceBehindRemoteAnimationRequested);
return;
@@ -3144,6 +3145,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// Post layout changes to the next frame, so we don't hang at the end of the animation.
DejankUtils.postAfterTraversal(() -> {
+ if (!mPM.isInteractive()) {
+ Log.e(TAG, "exitKeyguardAndFinishSurfaceBehindRemoteAnimation#postAfterTraversal" +
+ "Not interactive after traversal. Don't hide the keyguard. This means we " +
+ "re-locked the device during unlock.");
+ return;
+ }
+
onKeyguardExitFinished();
if (mKeyguardStateController.isDismissingFromSwipe() || wasShowing) {
@@ -3156,9 +3164,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
+ " wasShowing=" + wasShowing);
}
- mKeyguardUnlockAnimationControllerLazy.get()
- .notifyFinishedKeyguardExitAnimation(cancelled);
- finishSurfaceBehindRemoteAnimation(cancelled);
+ finishSurfaceBehindRemoteAnimation(showKeyguard);
// Dispatch the callback on animation finishes.
mUpdateMonitor.dispatchKeyguardDismissAnimationFinished();
@@ -3222,7 +3228,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
* This does not set keyguard state to either locked or unlocked, it simply ends the remote
* animation on the surface behind the keyguard. This can be called by
*/
- void finishSurfaceBehindRemoteAnimation(boolean cancelled) {
+ void finishSurfaceBehindRemoteAnimation(boolean showKeyguard) {
+ mKeyguardUnlockAnimationControllerLazy.get()
+ .notifyFinishedKeyguardExitAnimation(showKeyguard);
+
mSurfaceBehindRemoteAnimationRequested = false;
mSurfaceBehindRemoteAnimationRunning = false;
mKeyguardStateController.notifyKeyguardGoingAway(false);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
index 74ef7a50fd44..fe778a6e1ccd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
@@ -60,6 +60,7 @@ interface KeyguardFaceAuthInteractor {
fun onNotificationPanelClicked()
fun onSwipeUpOnBouncer()
fun onPrimaryBouncerUserInput()
+ fun onAccessibilityAction()
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
index 5005b6c7f0df..69474e7f93b3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
@@ -60,4 +60,5 @@ class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInt
override fun onSwipeUpOnBouncer() {}
override fun onPrimaryBouncerUserInput() {}
+ override fun onAccessibilityAction() {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
index 6b515dab79f6..bcd11a700898 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -133,6 +133,10 @@ constructor(
runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN, false)
}
+ override fun onAccessibilityAction() {
+ runFaceAuth(FaceAuthUiEvent.FACE_AUTH_ACCESSIBILITY_ACTION, false)
+ }
+
override fun registerListener(listener: FaceAuthenticationListener) {
listeners.add(listener)
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 0261ee53a0ff..1e5170ab1b6f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -120,6 +120,14 @@ public class LogModule {
return factory.create("ShadeLog", 500, false);
}
+ /** Provides a logging buffer for Shade messages. */
+ @Provides
+ @SysUISingleton
+ @ShadeTouchLog
+ public static LogBuffer provideShadeTouchLogBuffer(LogBufferFactory factory) {
+ return factory.create("ShadeTouchLog", 500, false);
+ }
+
/** Provides a logging buffer for all logs related to managing notification sections. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeTouchLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeTouchLog.java
new file mode 100644
index 000000000000..b13667e4822b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeTouchLog.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for tracking touches in various shade child views. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface ShadeTouchLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 1afc885cb70d..d2eac45754bd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -23,12 +23,14 @@ import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.PointF;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.qs.customize.QSCustomizer;
+import com.android.systemui.shade.TouchLogger;
import com.android.systemui.util.LargeScreenUtils;
import java.io.PrintWriter;
@@ -129,6 +131,11 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
}
@Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return TouchLogger.logDispatchTouch("QS", ev, super.dispatchTouchEvent(ev));
+ }
+
+ @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
updateExpansion();
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
index fc89a9e637ec..f4d19dc2691d 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
@@ -30,6 +30,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
@@ -38,6 +39,7 @@ import androidx.core.graphics.ColorUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
+import com.android.systemui.shade.TouchLogger;
import com.android.systemui.util.LargeScreenUtils;
import java.util.concurrent.Executor;
@@ -59,6 +61,7 @@ public class ScrimView extends View {
private float mViewAlpha = 1.0f;
private Drawable mDrawable;
private PorterDuffColorFilter mColorFilter;
+ private String mScrimName;
private int mTintColor;
private boolean mBlendWithMainColor = true;
private Runnable mChangeRunnable;
@@ -336,6 +339,15 @@ public class ScrimView extends View {
}
}
+ public void setScrimName(String scrimName) {
+ mScrimName = scrimName;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return TouchLogger.logDispatchTouch(mScrimName, ev, super.dispatchTouchEvent(ev));
+ }
+
/**
* The position of the bottom of the scrim, used for clipping.
* @see #enableBottomEdgeConcave(boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt
index 468a75d8276e..e7ee961e1888 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt
@@ -48,6 +48,9 @@ interface DisplayTracker {
/** Remove a [Callback] previously added. */
fun removeCallback(callback: Callback)
+ /** Gets the Display with the given displayId */
+ fun getDisplay(displayId: Int): Display
+
/** Ćallback for notifying of changes. */
interface Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt
index 5169f88c373c..68cc483fbe80 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt
@@ -115,6 +115,10 @@ internal constructor(
}
}
+ override fun getDisplay(displayId: Int): Display {
+ return displayManager.getDisplay(displayId)
+ }
+
@WorkerThread
private fun onDisplayAdded(displayId: Int, list: List<DisplayTrackerDataItem>) {
Assert.isNotMainThread()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
index af3cc860df57..c501d88b77ce 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
@@ -106,6 +106,11 @@ public final class NotificationPanelView extends FrameLayout {
}
@Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return TouchLogger.logDispatchTouch("NPV", ev, super.dispatchTouchEvent(ev));
+ }
+
+ @Override
public void dispatchConfigurationChanged(Configuration newConfig) {
super.dispatchConfigurationChanged(newConfig);
mOnConfigurationChangedListener.onConfigurationChanged(newConfig);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index d75190e7289a..7e455ca896b5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -192,6 +192,8 @@ public class NotificationShadeWindowView extends FrameLayout {
result = result != null ? result : super.dispatchTouchEvent(ev);
+ TouchLogger.logDispatchTouch(TAG, ev, result);
+
mInteractionEventHandler.dispatchTouchEventComplete();
return result;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 2f7644eb5c82..4260bd97759d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -92,6 +92,7 @@ public class NotificationShadeWindowViewController {
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
private final LockIconViewController mLockIconViewController;
+ private final ShadeLogger mShadeLogger;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final StatusBarWindowStateController mStatusBarWindowStateController;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@@ -104,7 +105,14 @@ public class NotificationShadeWindowViewController {
private boolean mTouchActive;
private boolean mTouchCancelled;
private MotionEvent mDownEvent;
+ // TODO rename to mLaunchAnimationRunning
private boolean mExpandAnimationRunning;
+ /**
+ * When mExpandAnimationRunning is true and the touch dispatcher receives a down even after
+ * uptime exceeds this, the dispatcher will stop blocking touches for the launch animation,
+ * which has presumabely not completed due to an error.
+ */
+ private long mLaunchAnimationTimeout;
private NotificationStackScrollLayout mStackScrollLayout;
private PhoneStatusBarViewController mStatusBarViewController;
private final CentralSurfaces mService;
@@ -146,6 +154,7 @@ public class NotificationShadeWindowViewController {
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
NotificationInsetsController notificationInsetsController,
AmbientState ambientState,
+ ShadeLogger shadeLogger,
PulsingGestureListener pulsingGestureListener,
KeyguardBouncerViewModel keyguardBouncerViewModel,
KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory,
@@ -168,6 +177,7 @@ public class NotificationShadeWindowViewController {
mStatusBarWindowStateController = statusBarWindowStateController;
mLockIconViewController = lockIconViewController;
mLockIconViewController.init();
+ mShadeLogger = shadeLogger;
mService = centralSurfaces;
mNotificationShadeWindowController = controller;
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
@@ -214,6 +224,13 @@ public class NotificationShadeWindowViewController {
return mView.findViewById(R.id.keyguard_message_area);
}
+ private Boolean logDownDispatch(MotionEvent ev, String msg, Boolean result) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mShadeLogger.logShadeWindowDispatch(ev, msg, result);
+ }
+ return result;
+ }
+
/** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */
public void setupExpandedStatusBar() {
mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
@@ -225,8 +242,8 @@ public class NotificationShadeWindowViewController {
@Override
public Boolean handleDispatchTouchEvent(MotionEvent ev) {
if (mStatusBarViewController == null) { // Fix for b/192490822
- Log.w(TAG, "Ignoring touch while statusBarView not yet set.");
- return false;
+ return logDownDispatch(ev,
+ "Ignoring touch while statusBarView not yet set", false);
}
boolean isDown = ev.getActionMasked() == MotionEvent.ACTION_DOWN;
boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
@@ -238,10 +255,9 @@ public class NotificationShadeWindowViewController {
}
// Reset manual touch dispatch state here but make sure the UP/CANCEL event still
- // gets
- // delivered.
+ // gets delivered.
if (!isCancel && mService.shouldIgnoreTouch()) {
- return false;
+ return logDownDispatch(ev, "touch ignored by CS", false);
}
if (isDown) {
@@ -253,8 +269,16 @@ public class NotificationShadeWindowViewController {
mTouchActive = false;
mDownEvent = null;
}
- if (mTouchCancelled || mExpandAnimationRunning) {
- return false;
+ if (mTouchCancelled) {
+ return logDownDispatch(ev, "touch cancelled", false);
+ }
+ if (mExpandAnimationRunning) {
+ if (isDown && mClock.uptimeMillis() > mLaunchAnimationTimeout) {
+ mShadeLogger.d("NSWVC: launch animation timed out");
+ setExpandAnimationRunning(false);
+ } else {
+ return logDownDispatch(ev, "expand animation running", false);
+ }
}
if (mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {
@@ -268,13 +292,13 @@ public class NotificationShadeWindowViewController {
}
if (mIsOcclusionTransitionRunning) {
- return false;
+ return logDownDispatch(ev, "occlusion transition running", false);
}
mFalsingCollector.onTouchEvent(ev);
mPulsingWakeupGestureHandler.onTouchEvent(ev);
if (mStatusBarKeyguardViewManager.dispatchTouchEvent(ev)) {
- return true;
+ return logDownDispatch(ev, "dispatched to Keyguard", true);
}
if (mBrightnessMirror != null
&& mBrightnessMirror.getVisibility() == View.VISIBLE) {
@@ -282,7 +306,7 @@ public class NotificationShadeWindowViewController {
// you can't touch anything other than the brightness slider while the mirror is
// showing and the rest of the panel is transparent.
if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
- return false;
+ return logDownDispatch(ev, "disallowed new pointer", false);
}
}
if (isDown) {
@@ -314,7 +338,9 @@ public class NotificationShadeWindowViewController {
expandingBelowNotch = true;
}
if (expandingBelowNotch) {
- return mStatusBarViewController.sendTouchToView(ev);
+ return logDownDispatch(ev,
+ "expand below notch. sending touch to status bar",
+ mStatusBarViewController.sendTouchToView(ev));
}
if (!mIsTrackingBarGesture && isDown
@@ -324,9 +350,10 @@ public class NotificationShadeWindowViewController {
if (mStatusBarViewController.touchIsWithinView(x, y)) {
if (mStatusBarWindowStateController.windowIsShowing()) {
mIsTrackingBarGesture = true;
- return mStatusBarViewController.sendTouchToView(ev);
- } else { // it's hidden or hiding, don't send to notification shade.
- return true;
+ return logDownDispatch(ev, "sending touch to status bar",
+ mStatusBarViewController.sendTouchToView(ev));
+ } else {
+ return logDownDispatch(ev, "hidden or hiding", true);
}
}
} else if (mIsTrackingBarGesture) {
@@ -334,10 +361,10 @@ public class NotificationShadeWindowViewController {
if (isUp || isCancel) {
mIsTrackingBarGesture = false;
}
- return sendToStatusBar;
+ return logDownDispatch(ev, "sending bar gesture to status bar",
+ sendToStatusBar);
}
-
- return null;
+ return logDownDispatch(ev, "no custom touch dispatch of down event", null);
}
@Override
@@ -349,18 +376,26 @@ public class NotificationShadeWindowViewController {
public boolean shouldInterceptTouchEvent(MotionEvent ev) {
if (mStatusBarStateController.isDozing() && !mService.isPulsing()
&& !mDockManager.isDocked()) {
- // Capture all touch events in always-on.
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mShadeLogger.d("NSWVC: capture all touch events in always-on");
+ }
return true;
}
if (mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(ev)) {
// Don't allow touches to proceed to underlying views if alternate
// bouncer is showing
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mShadeLogger.d("NSWVC: alt bouncer showing");
+ }
return true;
}
if (mLockIconViewController.onInterceptTouchEvent(ev)) {
// immediately return true; don't send the touch to the drag down helper
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mShadeLogger.d("NSWVC: don't send touch to drag down helper");
+ }
return true;
}
@@ -371,7 +406,13 @@ public class NotificationShadeWindowViewController {
&& mDragDownHelper.isDragDownEnabled()
&& !mService.isBouncerShowing()
&& !mStatusBarStateController.isDozing()) {
- return mDragDownHelper.onInterceptTouchEvent(ev);
+ boolean result = mDragDownHelper.onInterceptTouchEvent(ev);
+ if (result) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mShadeLogger.d("NSWVC: drag down helper intercepted");
+ }
+ }
+ return result;
} else {
return false;
}
@@ -486,6 +527,7 @@ public class NotificationShadeWindowViewController {
}
public void cancelCurrentTouch() {
+ mShadeLogger.d("NSWVC: cancelling current touch");
if (mTouchActive) {
final long now = mClock.uptimeMillis();
final MotionEvent event;
@@ -499,6 +541,7 @@ public class NotificationShadeWindowViewController {
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
}
+ Log.w(TAG, "Canceling current touch event (should be very rare)");
mView.dispatchTouchEvent(event);
event.recycle();
mTouchCancelled = true;
@@ -517,6 +560,9 @@ public class NotificationShadeWindowViewController {
public void setExpandAnimationRunning(boolean running) {
if (mExpandAnimationRunning != running) {
+ if (running) {
+ mLaunchAnimationTimeout = mClock.uptimeMillis() + 5000;
+ }
mExpandAnimationRunning = running;
mNotificationShadeWindowController.setLaunchingActivity(mExpandAnimationRunning);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index e5b84bd86514..600a026eebee 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -22,6 +22,7 @@ import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import android.view.WindowInsets;
@@ -174,6 +175,12 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
}
@Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return TouchLogger.logDispatchTouch("NotificationsQuickSettingsContainer", ev,
+ super.dispatchTouchEvent(ev));
+ }
+
+ @Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
int layoutIndex = mLayoutDrawingOrder.indexOf(child);
if (layoutIndex >= 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index d00dab633014..3be24ba502a3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -25,6 +25,8 @@ import android.view.WindowManagerGlobal;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.dagger.ShadeTouchLog;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationPresenter;
@@ -72,6 +74,7 @@ public final class ShadeControllerImpl implements ShadeController {
@Inject
public ShadeControllerImpl(
CommandQueue commandQueue,
+ @ShadeTouchLog LogBuffer touchLog,
KeyguardStateController keyguardStateController,
StatusBarStateController statusBarStateController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@@ -82,6 +85,7 @@ public final class ShadeControllerImpl implements ShadeController {
Lazy<NotificationGutsManager> gutsManager
) {
mCommandQueue = commandQueue;
+ TouchLogger.logTouchesTo(touchLog);
mStatusBarStateController = statusBarStateController;
mStatusBarWindowController = statusBarWindowController;
mGutsManager = gutsManager;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 4fdd6e19cc4c..40a9035bbe06 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -79,19 +79,39 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) {
fun logMotionEvent(event: MotionEvent, message: String) {
buffer.log(
- TAG,
- LogLevel.VERBOSE,
- {
- str1 = message
- long1 = event.eventTime
- long2 = event.downTime
- int1 = event.action
- int2 = event.classification
- double1 = event.y.toDouble()
- },
- {
- "$str1: eventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2"
- }
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = message
+ long1 = event.eventTime
+ long2 = event.downTime
+ int1 = event.action
+ int2 = event.classification
+ },
+ {
+ "$str1: eventTime=$long1,downTime=$long2,action=$int1,class=$int2"
+ }
+ )
+ }
+
+ /** Logs motion event dispatch results from NotificationShadeWindowViewController. */
+ fun logShadeWindowDispatch(event: MotionEvent, message: String, result: Boolean?) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = message
+ long1 = event.eventTime
+ long2 = event.downTime
+ },
+ {
+ val prefix = when (result) {
+ true -> "SHADE TOUCH REROUTED"
+ false -> "SHADE TOUCH BLOCKED"
+ null -> "SHADE TOUCH DISPATCHED"
+ }
+ "$prefix: eventTime=$long1,downTime=$long2, reason=$str1"
+ }
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/TouchLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/TouchLogger.kt
new file mode 100644
index 000000000000..8aa2ee4dd9ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/TouchLogger.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.systemui.shade
+
+import android.view.MotionEvent
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+
+private const val TAG = "systemui.shade.touch"
+
+/**
+ * A logger for tracking touch dispatching in the shade view hierarchy. The purpose of this logger
+ * is to passively observe dispatchTouchEvent calls in order to see which subtrees of the shade are
+ * handling touches. Additionally, some touches may be passively observed for views near the top of
+ * the shade hierarchy that cannot intercept touches, i.e. scrims. The usage of static methods for
+ * logging is sub-optimal in many ways, but it was selected in this case to make usage of this
+ * non-function diagnostic code as low friction as possible.
+ */
+class TouchLogger {
+ companion object {
+ private var touchLogger: DispatchTouchLogger? = null
+
+ @JvmStatic
+ fun logTouchesTo(buffer: LogBuffer) {
+ touchLogger = DispatchTouchLogger(buffer)
+ }
+
+ @JvmStatic
+ fun logDispatchTouch(viewTag: String, ev: MotionEvent, result: Boolean): Boolean {
+ touchLogger?.logDispatchTouch(viewTag, ev, result)
+ return result
+ }
+ }
+}
+
+/** Logs touches. */
+private class DispatchTouchLogger(private val buffer: LogBuffer) {
+ fun logDispatchTouch(viewTag: String, ev: MotionEvent, result: Boolean) {
+ // NOTE: never log position of touches for security purposes
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = viewTag
+ int1 = ev.action
+ long1 = ev.downTime
+ bool1 = result
+ },
+ { "Touch: view=$str1, type=${typeToString(int1)}, downtime=$long1, result=$bool1" }
+ )
+ }
+
+ private fun typeToString(type: Int): String {
+ return when (type) {
+ MotionEvent.ACTION_DOWN -> "DOWN"
+ MotionEvent.ACTION_UP -> "UP"
+ MotionEvent.ACTION_MOVE -> "MOVE"
+ MotionEvent.ACTION_CANCEL -> "CANCEL"
+ MotionEvent.ACTION_POINTER_DOWN -> "POINTER_DOWN"
+ MotionEvent.ACTION_POINTER_UP -> "POINTER_UP"
+ else -> "OTHER"
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index b847fb6ded83..03c458918f4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -1002,7 +1002,13 @@ public class KeyguardIndicationController {
return; // udfps affordance is highlighted, no need to show action to unlock
} else if (mKeyguardUpdateMonitor.isFaceEnrolled()
&& !mKeyguardUpdateMonitor.getIsFaceAuthenticated()) {
- String message = mContext.getString(R.string.keyguard_retry);
+ String message;
+ if (mAccessibilityManager.isEnabled()
+ || mAccessibilityManager.isTouchExplorationEnabled()) {
+ message = mContext.getString(R.string.accesssibility_keyguard_retry);
+ } else {
+ message = mContext.getString(R.string.keyguard_retry);
+ }
mStatusBarKeyguardViewManager.setKeyguardMessage(message, mInitialTextColorState);
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index b2b6e1424f98..3120128c7967 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -14,9 +14,11 @@ import android.graphics.Shader
import android.os.Trace
import android.util.AttributeSet
import android.util.MathUtils.lerp
+import android.view.MotionEvent
import android.view.View
import android.view.animation.PathInterpolator
import com.android.app.animation.Interpolators
+import com.android.systemui.shade.TouchLogger
import com.android.systemui.statusbar.LightRevealEffect.Companion.getPercentPastThreshold
import com.android.systemui.util.getColorWithAlpha
import com.android.systemui.util.leak.RotationUtils
@@ -234,6 +236,8 @@ class PowerButtonReveal(
}
}
+private const val TAG = "LightRevealScrim"
+
/**
* Scrim view that partially reveals the content underneath it using a [RadialGradient] with a
* transparent center. The center position, size, and stops of the gradient can be manipulated to
@@ -446,6 +450,10 @@ constructor(
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), gradientPaint)
}
+ override fun dispatchTouchEvent(event: MotionEvent): Boolean {
+ return TouchLogger.logDispatchTouch(TAG, event, super.dispatchTouchEvent(event))
+ }
+
private fun setPaintColorFilter() {
gradientPaint.colorFilter =
PorterDuffColorFilter(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index df6fe3d5e434..3862f5f4004c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -92,6 +92,7 @@ import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.TouchLogger;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
@@ -3457,6 +3458,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
return super.onTouchEvent(ev);
}
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
+ }
+
void dispatchDownEventToScroller(MotionEvent ev) {
MotionEvent downEvent = MotionEvent.obtain(ev);
downEvent.setAction(MotionEvent.ACTION_DOWN);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index c42a6dc5b6f2..c0b5fdfb5436 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -360,6 +360,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
mScrimBehind = behindScrim;
mScrimInFront = scrimInFront;
updateThemeColors();
+ mNotificationsScrim.setScrimName(getScrimName(mNotificationsScrim));
+ mScrimBehind.setScrimName(getScrimName(mScrimBehind));
+ mScrimInFront.setScrimName(getScrimName(mScrimInFront));
behindScrim.enableBottomEdgeConcave(mClipsQsScrim);
mNotificationsScrim.enableRoundedCorners(true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 5a90edde342e..50172ddd3673 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -121,6 +121,7 @@ class UnlockedScreenOffAnimationController @Inject constructor(
// FrameCallback used to delay starting the light reveal animation until the next frame
private val startLightRevealCallback = TraceUtils.namedRunnable("startLightReveal") {
+ lightRevealAnimationPlaying = true
lightRevealAnimator.start()
}
@@ -258,7 +259,6 @@ class UnlockedScreenOffAnimationController @Inject constructor(
decidedToAnimateGoingToSleep = true
shouldAnimateInKeyguard = true
- lightRevealAnimationPlaying = true
// Start the animation on the next frame. startAnimation() is called after
// PhoneWindowManager makes a binder call to System UI on
@@ -273,7 +273,8 @@ class UnlockedScreenOffAnimationController @Inject constructor(
// dispatched, a race condition could make it possible for this callback to be run
// as the device is waking up. That results in the AOD UI being shown while we wake
// up, with unpredictable consequences.
- if (!powerManager.isInteractive(Display.DEFAULT_DISPLAY)) {
+ if (!powerManager.isInteractive(Display.DEFAULT_DISPLAY) &&
+ shouldAnimateInKeyguard) {
aodUiAnimationPlaying = true
// Show AOD. That'll cause the KeyguardVisibilityHelper to call
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
index 640adcc9dd94..5e489b0f38ac 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
@@ -20,15 +20,14 @@ import com.android.systemui.dagger.DefaultComponentBinder;
import com.android.systemui.dagger.DependencyProvider;
import com.android.systemui.dagger.SysUIComponent;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.SystemUIBinder;
import com.android.systemui.dagger.SystemUIModule;
+import com.android.systemui.globalactions.ShutdownUiModule;
+import com.android.systemui.keyguard.dagger.KeyguardModule;
+import com.android.systemui.recents.RecentsModule;
import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.row.NotificationRowModule;
-import com.android.systemui.keyguard.dagger.KeyguardModule;
-import com.android.systemui.recents.RecentsModule;
-
import dagger.Subcomponent;
/**
@@ -43,6 +42,7 @@ import dagger.Subcomponent;
NotificationRowModule.class,
NotificationsModule.class,
RecentsModule.class,
+ ShutdownUiModule.class,
SystemUIModule.class,
TvSystemUIBinder.class,
TVSystemUICoreStartableModule.class,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index d9d0b4e661ff..54a8b31bb599 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -60,6 +60,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate;
import com.android.systemui.biometrics.SideFpsController;
import com.android.systemui.biometrics.SideFpsUiRequestSource;
import com.android.systemui.classifier.FalsingA11yDelegate;
@@ -159,6 +160,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
private ViewMediatorCallback mViewMediatorCallback;
@Mock
private AudioManager mAudioManager;
+ @Mock
+ private FaceAuthAccessibilityDelegate mFaceAuthAccessibilityDelegate;
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback;
@@ -217,7 +220,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
mUserSwitcherController, mock(DeviceProvisionedController.class), mFeatureFlags, mGlobalSettings,
mSessionTracker, Optional.of(mSideFpsController), mFalsingA11yDelegate,
mTelephonyManager, mViewMediatorCallback, mAudioManager,
- mock(KeyguardFaceAuthInteractor.class));
+ mock(KeyguardFaceAuthInteractor.class),
+ mFaceAuthAccessibilityDelegate);
}
@Test
@@ -686,6 +690,11 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
verify(mView).setTranslationY(0f);
}
+ @Test
+ public void setAccessibilityDelegate() {
+ verify(mView).setAccessibilityDelegate(eq(mFaceAuthAccessibilityDelegate));
+ }
+
private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() {
mKeyguardSecurityContainerController.onViewAttached();
verify(mView).setSwipeListener(mSwipeListenerArgumentCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 3cb4c0c51252..1a9ac0b7b0a6 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -38,6 +38,7 @@ import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELL
import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
+import static com.android.systemui.flags.Flags.STOP_FACE_AUTH_ON_DISPLAY_OFF;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
@@ -84,6 +85,7 @@ import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.display.DisplayManagerGlobal;
import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
@@ -115,6 +117,9 @@ import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.Display;
+import android.view.DisplayAdjustments;
+import android.view.DisplayInfo;
import androidx.annotation.NonNull;
@@ -134,8 +139,10 @@ import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -288,10 +295,13 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mFingerprintAuthenticatorsRegisteredCallback;
private IFaceAuthenticatorsRegisteredCallback mFaceAuthenticatorsRegisteredCallback;
private final InstanceId mKeyguardInstanceId = InstanceId.fakeInstanceId(999);
+ private FakeFeatureFlags mFeatureFlags;
+ private FakeDisplayTracker mDisplayTracker;
@Before
public void setup() throws RemoteException {
MockitoAnnotations.initMocks(this);
+ mDisplayTracker = new FakeDisplayTracker(mContext);
when(mSessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(mKeyguardInstanceId);
when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true);
@@ -326,6 +336,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mTestableLooper = TestableLooper.get(this);
allowTestableLooperAsMainThread();
+ mFeatureFlags = new FakeFeatureFlags();
+ mFeatureFlags.set(STOP_FACE_AUTH_ON_DISPLAY_OFF, false);
when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI);
@@ -336,6 +348,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
anyInt());
mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
+ setupBiometrics(mKeyguardUpdateMonitor);
+ }
+
+ private void setupBiometrics(KeyguardUpdateMonitor keyguardUpdateMonitor)
+ throws RemoteException {
captureAuthenticatorsRegisteredCallbacks();
setupFaceAuth(/* isClass3 */ false);
setupFingerprintAuth(/* isClass3 */ true);
@@ -345,9 +362,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mBiometricEnabledOnKeyguardCallback = mBiometricEnabledCallbackArgCaptor.getValue();
biometricsEnabledForCurrentUser();
- mHandler = spy(mKeyguardUpdateMonitor.getHandler());
+ mHandler = spy(keyguardUpdateMonitor.getHandler());
try {
- FieldSetter.setField(mKeyguardUpdateMonitor,
+ FieldSetter.setField(keyguardUpdateMonitor,
KeyguardUpdateMonitor.class.getDeclaredField("mHandler"), mHandler);
} catch (NoSuchFieldException e) {
@@ -2945,6 +2962,79 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
TelephonyManager.SIM_STATE_NOT_READY);
}
+ @Test
+ public void stopFaceAuthOnDisplayOffFlagNotEnabled_doNotRegisterForDisplayCallback() {
+ assertThat(mDisplayTracker.getDisplayCallbacks().size()).isEqualTo(0);
+ }
+
+ @Test
+ public void onDisplayOn_nothingHappens() throws RemoteException {
+ // GIVEN
+ keyguardIsVisible();
+ enableStopFaceAuthOnDisplayOff();
+
+ // WHEN the default display state changes to ON
+ triggerDefaultDisplayStateChangeToOn();
+
+ // THEN face auth is NOT started since we rely on STARTED_WAKING_UP to start face auth,
+ // NOT the display on event
+ verifyFaceAuthenticateNeverCalled();
+ verifyFaceDetectNeverCalled();
+ }
+
+ @Test
+ public void onDisplayOff_stopFaceAuth() throws RemoteException {
+ enableStopFaceAuthOnDisplayOff();
+
+ // GIVEN device is listening for face
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mTestableLooper.processAllMessages();
+ verifyFaceAuthenticateCall();
+
+ final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
+ mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN the default display state changes to OFF
+ triggerDefaultDisplayStateChangeToOff();
+
+ // THEN face listening is stopped.
+ verify(faceCancel).cancel();
+ verify(callback).onBiometricRunningStateChanged(
+ eq(false), eq(BiometricSourceType.FACE)); // beverlyt
+
+ }
+
+ private void triggerDefaultDisplayStateChangeToOn() {
+ triggerDefaultDisplayStateChangeTo(true);
+ }
+
+ private void triggerDefaultDisplayStateChangeToOff() {
+ triggerDefaultDisplayStateChangeTo(false);
+ }
+
+ /**
+ * @param on true for Display.STATE_ON, else Display.STATE_OFF
+ */
+ private void triggerDefaultDisplayStateChangeTo(boolean on) {
+ DisplayManagerGlobal displayManagerGlobal = mock(DisplayManagerGlobal.class);
+ DisplayInfo displayInfoWithDisplayState = new DisplayInfo();
+ displayInfoWithDisplayState.state = on ? Display.STATE_ON : Display.STATE_OFF;
+ when(displayManagerGlobal.getDisplayInfo(mDisplayTracker.getDefaultDisplayId()))
+ .thenReturn(displayInfoWithDisplayState);
+ mDisplayTracker.setAllDisplays(new Display[]{
+ new Display(
+ displayManagerGlobal,
+ mDisplayTracker.getDefaultDisplayId(),
+ displayInfoWithDisplayState,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+ )
+ });
+ mDisplayTracker.triggerOnDisplayChanged(mDisplayTracker.getDefaultDisplayId());
+ }
+
private void verifyFingerprintAuthenticateNeverCalled() {
verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any());
verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
@@ -3213,6 +3303,18 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mTestableLooper.processAllMessages();
}
+ private void enableStopFaceAuthOnDisplayOff() throws RemoteException {
+ cleanupKeyguardUpdateMonitor();
+ clearInvocations(mFaceManager);
+ clearInvocations(mFingerprintManager);
+ clearInvocations(mBiometricManager);
+ clearInvocations(mStatusBarStateController);
+ mFeatureFlags.set(STOP_FACE_AUTH_ON_DISPLAY_OFF, true);
+ mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
+ setupBiometrics(mKeyguardUpdateMonitor);
+ assertThat(mDisplayTracker.getDisplayCallbacks().size()).isEqualTo(1);
+ }
+
private Intent putPhoneInfo(Intent intent, Bundle data, Boolean simInited) {
int subscription = simInited
? 1/* mock subid=1 */ : SubscriptionManager.PLACEHOLDER_SUBSCRIPTION_ID_BASE;
@@ -3272,7 +3374,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mDreamManager, mDevicePolicyManager, mSensorPrivacyManager, mTelephonyManager,
mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager,
mFaceWakeUpTriggersConfig, mDevicePostureController,
- Optional.of(mInteractiveToAuthProvider));
+ Optional.of(mInteractiveToAuthProvider), mFeatureFlags,
+ mDisplayTracker);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
index 01d3a3931052..ea7cc3dcd119 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
@@ -27,6 +27,8 @@ import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.biometrics.AuthController
import com.android.systemui.decor.FaceScanningProviderFactory
import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.util.mockito.whenever
@@ -80,6 +82,8 @@ class FaceScanningProviderFactoryTest : SysuiTestCase() {
R.bool.config_fillMainBuiltInDisplayCutout,
true
)
+ val featureFlags = FakeFeatureFlags()
+ featureFlags.set(Flags.STOP_PULSING_FACE_SCANNING_ANIMATION, true)
underTest =
FaceScanningProviderFactory(
authController,
@@ -87,7 +91,8 @@ class FaceScanningProviderFactoryTest : SysuiTestCase() {
statusBarStateController,
keyguardUpdateMonitor,
mock(Executor::class.java),
- ScreenDecorationsLogger(logcatLogBuffer("FaceScanningProviderFactoryTest"))
+ ScreenDecorationsLogger(logcatLogBuffer("FaceScanningProviderFactoryTest")),
+ featureFlags,
)
whenever(authController.faceSensorLocation).thenReturn(Point(10, 10))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 4cf5a4be0b60..13a1fe969ac8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -91,6 +91,8 @@ import com.android.systemui.decor.OverlayWindow;
import com.android.systemui.decor.PrivacyDotCornerDecorProviderImpl;
import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
import com.android.systemui.decor.RoundedCornerResDelegate;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.log.ScreenDecorationsLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.FakeDisplayTracker;
@@ -225,13 +227,16 @@ public class ScreenDecorationsTest extends SysuiTestCase {
doAnswer(it -> !(mMockCutoutList.isEmpty())).when(mCutoutFactory).getHasProviders();
doReturn(mMockCutoutList).when(mCutoutFactory).getProviders();
+ FakeFeatureFlags featureFlags = new FakeFeatureFlags();
+ featureFlags.set(Flags.STOP_PULSING_FACE_SCANNING_ANIMATION, true);
mFaceScanningDecorProvider = spy(new FaceScanningOverlayProviderImpl(
BOUNDS_POSITION_TOP,
mAuthController,
mStatusBarStateController,
mKeyguardUpdateMonitor,
mExecutor,
- new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer"))));
+ new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
+ featureFlags));
mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings,
mTunerService, mUserTracker, mDisplayTracker, mDotViewController, mThreadFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index d31a86ae2809..e3e61306bcd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -51,6 +51,7 @@ import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -99,6 +100,8 @@ open class AuthContainerViewTest : SysuiTestCase() {
lateinit var windowToken: IBinder
@Mock
lateinit var interactionJankMonitor: InteractionJankMonitor
+ @Mock
+ lateinit var vibrator: VibratorHelper
// TODO(b/278622168): remove with flag
open val useNewBiometricPrompt = false
@@ -325,7 +328,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK or
BiometricManager.Authenticators.DEVICE_CREDENTIAL
)
- container.animateToCredentialUI()
+ container.animateToCredentialUI(false)
waitForIdleSync()
assertThat(container.hasCredentialView()).isTrue()
@@ -514,7 +517,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
{ authBiometricFingerprintViewModel },
{ promptSelectorInteractor },
{ bpCredentialInteractor },
- PromptViewModel(promptSelectorInteractor),
+ PromptViewModel(promptSelectorInteractor, vibrator),
{ credentialViewModel },
Handler(TestableLooper.get(this).looper),
fakeExecutor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index b9f92a064bc8..8278135b2f9f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -99,7 +99,6 @@ import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
import com.android.systemui.util.concurrency.FakeExecution;
@@ -191,6 +190,10 @@ public class AuthControllerTest extends SysuiTestCase {
private ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mFaceAuthenticatorsRegisteredCaptor;
@Captor
private ArgumentCaptor<BiometricStateListener> mBiometricStateCaptor;
+ @Captor
+ private ArgumentCaptor<Integer> mModalityCaptor;
+ @Captor
+ private ArgumentCaptor<String> mMessageCaptor;
@Mock
private Resources mResources;
@@ -202,9 +205,6 @@ public class AuthControllerTest extends SysuiTestCase {
private TestableAuthController mAuthController;
private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
- @Mock
- private VibratorHelper mVibratorHelper;
-
@Before
public void setup() throws RemoteException {
// TODO(b/278622168): remove with flag
@@ -267,7 +267,6 @@ public class AuthControllerTest extends SysuiTestCase {
true /* supportsSelfIllumination */,
true /* resetLockoutRequireHardwareAuthToken */));
when(mFaceManager.getSensorPropertiesInternal()).thenReturn(faceProps);
- when(mVibratorHelper.hasVibrator()).thenReturn(true);
mAuthController = new TestableAuthController(mContextSpy);
@@ -482,16 +481,63 @@ public class AuthControllerTest extends SysuiTestCase {
BiometricConstants.BIOMETRIC_PAUSED_REJECTED,
0 /* vendorCode */);
- ArgumentCaptor<Integer> modalityCaptor = ArgumentCaptor.forClass(Integer.class);
- ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
- verify(mDialog1).onAuthenticationFailed(modalityCaptor.capture(), messageCaptor.capture());
+ verify(mDialog1).onAuthenticationFailed(mModalityCaptor.capture(), mMessageCaptor.capture());
- assertEquals(modalityCaptor.getValue().intValue(), modality);
- assertEquals(messageCaptor.getValue(),
+ assertEquals(mModalityCaptor.getValue().intValue(), modality);
+ assertEquals(mMessageCaptor.getValue(),
mContext.getString(R.string.biometric_not_recognized));
}
@Test
+ public void testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected_withPaused() {
+ testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected(
+ BiometricConstants.BIOMETRIC_PAUSED_REJECTED);
+ }
+
+ @Test
+ public void testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected_withTimeout() {
+ testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected(
+ BiometricConstants.BIOMETRIC_ERROR_TIMEOUT);
+ }
+
+ private void testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected(int error) {
+ final int modality = BiometricAuthenticator.TYPE_FACE;
+ final int userId = 0;
+
+ enrollFingerprintAndFace(userId);
+
+ showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
+
+ mAuthController.onBiometricError(modality, error, 0 /* vendorCode */);
+
+ verify(mDialog1).onAuthenticationFailed(mModalityCaptor.capture(), mMessageCaptor.capture());
+
+ assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
+ assertThat(mMessageCaptor.getValue()).isEqualTo(
+ mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead));
+ }
+
+ @Test
+ public void testOnAuthenticationFailedInvoked_whenFingerprintAuthRejected() {
+ final int modality = BiometricAuthenticator.TYPE_FINGERPRINT;
+ final int userId = 0;
+
+ enrollFingerprintAndFace(userId);
+
+ showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
+
+ mAuthController.onBiometricError(modality,
+ BiometricConstants.BIOMETRIC_PAUSED_REJECTED,
+ 0 /* vendorCode */);
+
+ verify(mDialog1).onAuthenticationFailed(mModalityCaptor.capture(), mMessageCaptor.capture());
+
+ assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
+ assertThat(mMessageCaptor.getValue()).isEqualTo(
+ mContext.getString(R.string.fingerprint_error_not_match));
+ }
+
+ @Test
public void testOnAuthenticationFailedInvoked_whenBiometricTimedOut() {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
final int modality = BiometricAuthenticator.TYPE_FACE;
@@ -499,13 +545,11 @@ public class AuthControllerTest extends SysuiTestCase {
final int vendorCode = 0;
mAuthController.onBiometricError(modality, error, vendorCode);
- ArgumentCaptor<Integer> modalityCaptor = ArgumentCaptor.forClass(Integer.class);
- ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
- verify(mDialog1).onAuthenticationFailed(modalityCaptor.capture(), messageCaptor.capture());
+ verify(mDialog1).onAuthenticationFailed(mModalityCaptor.capture(), mMessageCaptor.capture());
- assertEquals(modalityCaptor.getValue().intValue(), modality);
- assertEquals(messageCaptor.getValue(),
- FaceManager.getErrorString(mContext, error, vendorCode));
+ assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
+ assertThat(mMessageCaptor.getValue()).isEqualTo(
+ mContext.getString(R.string.biometric_not_recognized));
}
@Test
@@ -515,12 +559,10 @@ public class AuthControllerTest extends SysuiTestCase {
final String helpMessage = "help";
mAuthController.onBiometricHelp(modality, helpMessage);
- ArgumentCaptor<Integer> modalityCaptor = ArgumentCaptor.forClass(Integer.class);
- ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
- verify(mDialog1).onHelp(modalityCaptor.capture(), messageCaptor.capture());
+ verify(mDialog1).onHelp(mModalityCaptor.capture(), mMessageCaptor.capture());
- assertEquals(modalityCaptor.getValue().intValue(), modality);
- assertEquals(messageCaptor.getValue(), helpMessage);
+ assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
+ assertThat(mMessageCaptor.getValue()).isEqualTo(helpMessage);
}
@Test
@@ -531,12 +573,10 @@ public class AuthControllerTest extends SysuiTestCase {
final int vendorCode = 0;
mAuthController.onBiometricError(modality, error, vendorCode);
- ArgumentCaptor<Integer> modalityCaptor = ArgumentCaptor.forClass(Integer.class);
- ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
- verify(mDialog1).onError(modalityCaptor.capture(), messageCaptor.capture());
+ verify(mDialog1).onError(mModalityCaptor.capture(), mMessageCaptor.capture());
- assertEquals(modalityCaptor.getValue().intValue(), modality);
- assertEquals(messageCaptor.getValue(),
+ assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
+ assertThat(mMessageCaptor.getValue()).isEqualTo(
FaceManager.getErrorString(mContext, error, vendorCode));
}
@@ -550,7 +590,7 @@ public class AuthControllerTest extends SysuiTestCase {
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
verify(mDialog1, never()).onError(anyInt(), anyString());
- verify(mDialog1).animateToCredentialUI();
+ verify(mDialog1).animateToCredentialUI(eq(true));
}
@Test
@@ -563,7 +603,7 @@ public class AuthControllerTest extends SysuiTestCase {
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
verify(mDialog1, never()).onError(anyInt(), anyString());
- verify(mDialog1).animateToCredentialUI();
+ verify(mDialog1).animateToCredentialUI(eq(true));
}
@Test
@@ -578,7 +618,7 @@ public class AuthControllerTest extends SysuiTestCase {
mAuthController.onBiometricError(modality, error, vendorCode);
verify(mDialog1).onError(
eq(modality), eq(FaceManager.getErrorString(mContext, error, vendorCode)));
- verify(mDialog1, never()).animateToCredentialUI();
+ verify(mDialog1, never()).animateToCredentialUI(eq(true));
}
@Test
@@ -593,7 +633,7 @@ public class AuthControllerTest extends SysuiTestCase {
mAuthController.onBiometricError(modality, error, vendorCode);
verify(mDialog1).onError(
eq(modality), eq(FaceManager.getErrorString(mContext, error, vendorCode)));
- verify(mDialog1, never()).animateToCredentialUI();
+ verify(mDialog1, never()).animateToCredentialUI(eq(true));
}
@Test
@@ -998,6 +1038,31 @@ public class AuthControllerTest extends SysuiTestCase {
return HAT;
}
+ private void enrollFingerprintAndFace(final int userId) {
+
+ // Enroll fingerprint
+ verify(mFingerprintManager).registerBiometricStateListener(
+ mBiometricStateCaptor.capture());
+ assertFalse(mAuthController.isFingerprintEnrolled(userId));
+
+ mBiometricStateCaptor.getValue().onEnrollmentsChanged(userId,
+ 1 /* sensorId */, true /* hasEnrollments */);
+ waitForIdleSync();
+
+ assertTrue(mAuthController.isFingerprintEnrolled(userId));
+
+ // Enroll face
+ verify(mFaceManager).registerBiometricStateListener(
+ mBiometricStateCaptor.capture());
+ assertFalse(mAuthController.isFaceAuthEnrolled(userId));
+
+ mBiometricStateCaptor.getValue().onEnrollmentsChanged(userId,
+ 2 /* sensorId */, true /* hasEnrollments */);
+ waitForIdleSync();
+
+ assertTrue(mAuthController.isFaceAuthEnrolled(userId));
+ }
+
private final class TestableAuthController extends AuthController {
private int mBuildCount = 0;
private PromptInfo mLastBiometricPromptInfo;
@@ -1012,7 +1077,7 @@ public class AuthControllerTest extends SysuiTestCase {
() -> mBiometricPromptCredentialInteractor, () -> mPromptSelectionInteractor,
() -> mCredentialViewModel, () -> mPromptViewModel,
mInteractionJankMonitor, mHandler,
- mBackgroundExecutor, mVibratorHelper, mUdfpsUtils);
+ mBackgroundExecutor, mUdfpsUtils);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
index 38c9caf085e2..9cb3b1aa9a55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
@@ -30,7 +30,11 @@ import android.app.Notification;
import android.app.NotificationManager;
import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.biometrics.BiometricStateListener;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
import android.os.Handler;
+import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -69,6 +73,10 @@ public class BiometricNotificationServiceTest extends SysuiTestCase {
Optional<FingerprintReEnrollNotification> mFingerprintReEnrollNotificationOptional;
@Mock
FingerprintReEnrollNotification mFingerprintReEnrollNotification;
+ @Mock
+ FingerprintManager mFingerprintManager;
+ @Mock
+ FaceManager mFaceManager;
private static final String TAG = "BiometricNotificationService";
private static final int FACE_NOTIFICATION_ID = 1;
@@ -81,6 +89,8 @@ public class BiometricNotificationServiceTest extends SysuiTestCase {
private TestableLooper mLooper;
private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
private KeyguardStateController.Callback mKeyguardStateControllerCallback;
+ private BiometricStateListener mFaceStateListener;
+ private BiometricStateListener mFingerprintStateListener;
@Before
public void setUp() {
@@ -99,25 +109,37 @@ public class BiometricNotificationServiceTest extends SysuiTestCase {
mKeyguardUpdateMonitor, mKeyguardStateController, handler,
mNotificationManager,
broadcastReceiver,
- mFingerprintReEnrollNotificationOptional);
+ mFingerprintReEnrollNotificationOptional,
+ mFingerprintManager,
+ mFaceManager);
biometricNotificationService.start();
ArgumentCaptor<KeyguardUpdateMonitorCallback> updateMonitorCallbackArgumentCaptor =
ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
ArgumentCaptor<KeyguardStateController.Callback> stateControllerCallbackArgumentCaptor =
ArgumentCaptor.forClass(KeyguardStateController.Callback.class);
+ ArgumentCaptor<BiometricStateListener> faceStateListenerArgumentCaptor =
+ ArgumentCaptor.forClass(BiometricStateListener.class);
+ ArgumentCaptor<BiometricStateListener> fingerprintStateListenerArgumentCaptor =
+ ArgumentCaptor.forClass(BiometricStateListener.class);
verify(mKeyguardUpdateMonitor).registerCallback(
updateMonitorCallbackArgumentCaptor.capture());
verify(mKeyguardStateController).addCallback(
stateControllerCallbackArgumentCaptor.capture());
+ verify(mFaceManager).registerBiometricStateListener(
+ faceStateListenerArgumentCaptor.capture());
+ verify(mFingerprintManager).registerBiometricStateListener(
+ fingerprintStateListenerArgumentCaptor.capture());
+ mFaceStateListener = faceStateListenerArgumentCaptor.getValue();
+ mFingerprintStateListener = fingerprintStateListenerArgumentCaptor.getValue();
mKeyguardUpdateMonitorCallback = updateMonitorCallbackArgumentCaptor.getValue();
mKeyguardStateControllerCallback = stateControllerCallbackArgumentCaptor.getValue();
}
@Test
- public void testShowFingerprintReEnrollNotification() {
+ public void testShowFingerprintReEnrollNotification_onAcquiredReEnroll() {
when(mKeyguardStateController.isShowing()).thenReturn(false);
mKeyguardUpdateMonitorCallback.onBiometricHelp(
@@ -139,7 +161,7 @@ public class BiometricNotificationServiceTest extends SysuiTestCase {
.isEqualTo(ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG);
}
@Test
- public void testShowFaceReEnrollNotification() {
+ public void testShowFaceReEnrollNotification_onErrorReEnroll() {
when(mKeyguardStateController.isShowing()).thenReturn(false);
mKeyguardUpdateMonitorCallback.onBiometricError(
@@ -161,4 +183,52 @@ public class BiometricNotificationServiceTest extends SysuiTestCase {
.isEqualTo(ACTION_SHOW_FACE_REENROLL_DIALOG);
}
+ @Test
+ public void testCancelReEnrollmentNotification_onFaceEnrollmentStateChange() {
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+
+ mKeyguardUpdateMonitorCallback.onBiometricError(
+ BiometricFaceConstants.BIOMETRIC_ERROR_RE_ENROLL,
+ "Testing Face Re-enrollment" /* errString */,
+ BiometricSourceType.FACE
+ );
+ mKeyguardStateControllerCallback.onKeyguardShowingChanged();
+
+ mLooper.moveTimeForward(SHOW_NOTIFICATION_DELAY_MS);
+ mLooper.processAllMessages();
+
+ verify(mNotificationManager).notifyAsUser(eq(TAG), eq(FACE_NOTIFICATION_ID),
+ mNotificationArgumentCaptor.capture(), any());
+
+ mFaceStateListener.onEnrollmentsChanged(0 /* userId */, 0 /* sensorId */,
+ false /* hasEnrollments */);
+
+ verify(mNotificationManager).cancelAsUser(eq(TAG), eq(FACE_NOTIFICATION_ID),
+ eq(UserHandle.CURRENT));
+ }
+
+ @Test
+ public void testCancelReEnrollmentNotification_onFingerprintEnrollmentStateChange() {
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+
+ mKeyguardUpdateMonitorCallback.onBiometricHelp(
+ FINGERPRINT_ACQUIRED_RE_ENROLL,
+ "Testing Fingerprint Re-enrollment" /* errString */,
+ BiometricSourceType.FINGERPRINT
+ );
+ mKeyguardStateControllerCallback.onKeyguardShowingChanged();
+
+ mLooper.moveTimeForward(SHOW_NOTIFICATION_DELAY_MS);
+ mLooper.processAllMessages();
+
+ verify(mNotificationManager).notifyAsUser(eq(TAG), eq(FINGERPRINT_NOTIFICATION_ID),
+ mNotificationArgumentCaptor.capture(), any());
+
+ mFingerprintStateListener.onEnrollmentsChanged(0 /* userId */, 0 /* sensorId */,
+ false /* hasEnrollments */);
+
+ verify(mNotificationManager).cancelAsUser(eq(TAG), eq(FINGERPRINT_NOTIFICATION_ID),
+ eq(UserHandle.CURRENT));
+ }
+
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt
new file mode 100644
index 000000000000..ec17794d4ee2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.systemui.biometrics
+
+import android.testing.TestableLooper
+import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.FaceAuthApiRequestReason
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class FaceAuthAccessibilityDelegateTest : SysuiTestCase() {
+
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var hostView: View
+ @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
+ private lateinit var underTest: FaceAuthAccessibilityDelegate
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ FaceAuthAccessibilityDelegate(
+ context.resources,
+ keyguardUpdateMonitor,
+ faceAuthInteractor,
+ )
+ }
+
+ @Test
+ fun shouldListenForFaceTrue_onInitializeAccessibilityNodeInfo_clickActionAdded() {
+ whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
+
+ // WHEN node is initialized
+ val mockedNodeInfo = mock(AccessibilityNodeInfo::class.java)
+ underTest.onInitializeAccessibilityNodeInfo(hostView, mockedNodeInfo)
+
+ // THEN a11y action is added
+ val argumentCaptor = argumentCaptor<AccessibilityNodeInfo.AccessibilityAction>()
+ verify(mockedNodeInfo).addAction(argumentCaptor.capture())
+
+ // AND the a11y action is a click action
+ assertEquals(
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
+ argumentCaptor.value.id
+ )
+ }
+
+ @Test
+ fun shouldListenForFaceFalse_onInitializeAccessibilityNodeInfo_clickActionNotAdded() {
+ whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(false)
+
+ // WHEN node is initialized
+ val mockedNodeInfo = mock(AccessibilityNodeInfo::class.java)
+ underTest.onInitializeAccessibilityNodeInfo(hostView, mockedNodeInfo)
+
+ // THEN a11y action is NOT added
+ verify(mockedNodeInfo, never())
+ .addAction(any(AccessibilityNodeInfo.AccessibilityAction::class.java))
+ }
+
+ @Test
+ fun performAccessibilityAction_actionClick_retriesFaceAuth() {
+ whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
+
+ // WHEN click action is performed
+ underTest.performAccessibilityAction(
+ hostView,
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
+ null
+ )
+
+ // THEN retry face auth
+ verify(keyguardUpdateMonitor)
+ .requestFaceAuth(eq(FaceAuthApiRequestReason.ACCESSIBILITY_ACTION))
+ verify(faceAuthInteractor).onAccessibilityAction()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 78341915edb7..7578cc774c48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -442,6 +442,16 @@ public class UdfpsControllerTest extends SysuiTestCase {
}
@Test
+ public void showUdfpsOverlay_callsListener() throws RemoteException {
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mFgExecutor.runAllReady();
+
+ verify(mFingerprintManager).onUdfpsUiEvent(FingerprintManager.UDFPS_UI_OVERLAY_SHOWN,
+ TEST_REQUEST_ID, mOpticalProps.sensorId);
+ }
+
+ @Test
public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception {
mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
@@ -761,17 +771,20 @@ public class UdfpsControllerTest extends SysuiTestCase {
inOrder.verify(mAlternateTouchProvider).onUiReady();
inOrder.verify(mLatencyTracker).onActionEnd(
eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
- verify(mFingerprintManager, never()).onUiReady(anyLong(), anyInt());
+ verify(mFingerprintManager, never()).onUdfpsUiEvent(
+ eq(FingerprintManager.UDFPS_UI_READY), anyLong(), anyInt());
} else {
InOrder inOrder = inOrder(mFingerprintManager, mLatencyTracker);
- inOrder.verify(mFingerprintManager).onUiReady(eq(TEST_REQUEST_ID),
+ inOrder.verify(mFingerprintManager).onUdfpsUiEvent(
+ eq(FingerprintManager.UDFPS_UI_READY), eq(TEST_REQUEST_ID),
eq(testParams.sensorProps.sensorId));
inOrder.verify(mLatencyTracker).onActionEnd(
eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
verify(mAlternateTouchProvider, never()).onUiReady();
}
} else {
- verify(mFingerprintManager, never()).onUiReady(anyLong(), anyInt());
+ verify(mFingerprintManager, never()).onUdfpsUiEvent(
+ eq(FingerprintManager.UDFPS_UI_READY), anyLong(), anyInt());
verify(mAlternateTouchProvider, never()).onUiReady();
verify(mLatencyTracker, never()).onActionEnd(
eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
@@ -1327,12 +1340,22 @@ public class UdfpsControllerTest extends SysuiTestCase {
// WHEN ACTION_DOWN is received
when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
processorResultDown);
- MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
- mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+ MotionEvent event = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
mBiometricExecutor.runAllReady();
- downEvent.recycle();
- // THEN the touch is pilfered
+ // WHEN ACTION_MOVE is received after
+ final TouchProcessorResult processorResultUnchanged =
+ new TouchProcessorResult.ProcessedTouch(
+ InteractionEvent.UNCHANGED, 1 /* pointerId */, touchData);
+ when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+ processorResultUnchanged);
+ event.setAction(ACTION_MOVE);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
+ mBiometricExecutor.runAllReady();
+ event.recycle();
+
+ // THEN only pilfer once on the initial down
verify(mInputManager).pilferPointers(any());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
new file mode 100644
index 000000000000..0df4fbf86d98
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.data.repository
+
+import android.database.ContentObserver
+import android.os.Handler
+import android.provider.Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.mockito.captureMany
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.settings.SecureSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+private const val USER_ID = 8
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class FaceSettingsRepositoryImplTest : SysuiTestCase() {
+
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+ private val testScope = TestScope()
+
+ @Mock private lateinit var mainHandler: Handler
+ @Mock private lateinit var secureSettings: SecureSettings
+
+ private lateinit var repository: FaceSettingsRepositoryImpl
+
+ @Before
+ fun setup() {
+ repository = FaceSettingsRepositoryImpl(mainHandler, secureSettings)
+ }
+
+ @Test
+ fun createsOneRepositoryPerUser() =
+ testScope.runTest {
+ val userRepo = repository.forUser(USER_ID)
+
+ assertThat(userRepo.userId).isEqualTo(USER_ID)
+
+ assertThat(repository.forUser(USER_ID)).isSameInstanceAs(userRepo)
+ assertThat(repository.forUser(USER_ID + 1)).isNotSameInstanceAs(userRepo)
+ }
+
+ @Test
+ fun startsRepoImmediatelyWithAllSettingKeys() =
+ testScope.runTest {
+ val userRepo = repository.forUser(USER_ID)
+
+ val keys =
+ captureMany<String> {
+ verify(secureSettings)
+ .registerContentObserverForUser(capture(), anyBoolean(), any(), eq(USER_ID))
+ }
+
+ assertThat(keys).containsExactly(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION)
+ }
+
+ @Test
+ fun forwardsSettingsValues() = runTest {
+ val userRepo = repository.forUser(USER_ID)
+
+ val intAsBooleanSettings =
+ listOf(
+ FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION to
+ collectLastValue(userRepo.alwaysRequireConfirmationInApps)
+ )
+
+ for ((setting, accessor) in intAsBooleanSettings) {
+ val observer =
+ withArgCaptor<ContentObserver> {
+ verify(secureSettings)
+ .registerContentObserverForUser(
+ eq(setting),
+ anyBoolean(),
+ capture(),
+ eq(USER_ID)
+ )
+ }
+
+ for (value in listOf(true, false)) {
+ secureSettings.mockIntSetting(setting, if (value) 1 else 0)
+ observer.onChange(false)
+ assertThat(accessor()).isEqualTo(value)
+ }
+ }
+ }
+
+ private fun SecureSettings.mockIntSetting(key: String, value: Int) {
+ whenever(getIntForUser(eq(key), anyInt(), eq(USER_ID))).thenReturn(value)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
index 4836af635aed..ec7ce634fd78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.systemui.biometrics.data.repository
import android.hardware.biometrics.PromptInfo
@@ -5,12 +21,16 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -21,61 +41,109 @@ import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
+private const val USER_ID = 9
+private const val CHALLENGE = 90L
+
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class PromptRepositoryImplTest : SysuiTestCase() {
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+ private val testScope = TestScope()
+ private val faceSettings = FakeFaceSettingsRepository()
+
@Mock private lateinit var authController: AuthController
private lateinit var repository: PromptRepositoryImpl
@Before
fun setup() {
- repository = PromptRepositoryImpl(authController)
+ repository = PromptRepositoryImpl(faceSettings, authController)
}
@Test
- fun isShowing() = runBlockingTest {
- whenever(authController.isShowing).thenReturn(true)
+ fun isShowing() =
+ testScope.runTest {
+ whenever(authController.isShowing).thenReturn(true)
+
+ val values = mutableListOf<Boolean>()
+ val job = launch { repository.isShowing.toList(values) }
+ runCurrent()
- val values = mutableListOf<Boolean>()
- val job = launch { repository.isShowing.toList(values) }
- assertThat(values).containsExactly(true)
+ assertThat(values).containsExactly(true)
- withArgCaptor<AuthController.Callback> {
- verify(authController).addCallback(capture())
+ withArgCaptor<AuthController.Callback> {
+ verify(authController).addCallback(capture())
- value.onBiometricPromptShown()
- assertThat(values).containsExactly(true, true)
+ value.onBiometricPromptShown()
+ runCurrent()
+ assertThat(values).containsExactly(true, true)
- value.onBiometricPromptDismissed()
- assertThat(values).containsExactly(true, true, false).inOrder()
+ value.onBiometricPromptDismissed()
+ runCurrent()
+ assertThat(values).containsExactly(true, true, false).inOrder()
- job.cancel()
- verify(authController).removeCallback(eq(value))
+ job.cancel()
+ runCurrent()
+ verify(authController).removeCallback(eq(value))
+ }
}
- }
@Test
- fun setsAndUnsetsPrompt() = runBlockingTest {
- val kind = PromptKind.Pin
- val uid = 8
- val challenge = 90L
- val promptInfo = PromptInfo()
+ fun isConfirmationRequired_whenNotForced() =
+ testScope.runTest {
+ faceSettings.setUserSettings(USER_ID, alwaysRequireConfirmationInApps = false)
+ val isConfirmationRequired by collectLastValue(repository.isConfirmationRequired)
+
+ for (case in listOf(true, false)) {
+ repository.setPrompt(
+ PromptInfo().apply { isConfirmationRequested = case },
+ USER_ID,
+ CHALLENGE,
+ PromptKind.Biometric()
+ )
+
+ assertThat(isConfirmationRequired).isEqualTo(case)
+ }
+ }
- repository.setPrompt(promptInfo, uid, challenge, kind)
+ @Test
+ fun isConfirmationRequired_whenForced() =
+ testScope.runTest {
+ faceSettings.setUserSettings(USER_ID, alwaysRequireConfirmationInApps = true)
+ val isConfirmationRequired by collectLastValue(repository.isConfirmationRequired)
+
+ for (case in listOf(true, false)) {
+ repository.setPrompt(
+ PromptInfo().apply { isConfirmationRequested = case },
+ USER_ID,
+ CHALLENGE,
+ PromptKind.Biometric()
+ )
+
+ assertThat(isConfirmationRequired).isTrue()
+ }
+ }
- assertThat(repository.kind.value).isEqualTo(kind)
- assertThat(repository.userId.value).isEqualTo(uid)
- assertThat(repository.challenge.value).isEqualTo(challenge)
- assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo)
+ @Test
+ fun setsAndUnsetsPrompt() =
+ testScope.runTest {
+ val kind = PromptKind.Pin
+ val promptInfo = PromptInfo()
- repository.unsetPrompt()
+ repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind)
- assertThat(repository.promptInfo.value).isNull()
- assertThat(repository.userId.value).isNull()
- assertThat(repository.challenge.value).isNull()
- }
+ assertThat(repository.kind.value).isEqualTo(kind)
+ assertThat(repository.userId.value).isEqualTo(USER_ID)
+ assertThat(repository.challenge.value).isEqualTo(CHALLENGE)
+ assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo)
+
+ repository.unsetPrompt()
+
+ assertThat(repository.promptInfo.value).isNull()
+ assertThat(repository.userId.value).isNull()
+ assertThat(repository.challenge.value).isNull()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
index a62ea3b77fc2..81cbaeab2a32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
@@ -106,17 +106,11 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() {
val currentPrompt by collectLastValue(interactor.prompt)
val credentialKind by collectLastValue(interactor.credentialKind)
val isCredentialAllowed by collectLastValue(interactor.isCredentialAllowed)
- val isExplicitConfirmationRequired by collectLastValue(interactor.isConfirmationRequested)
+ val isExplicitConfirmationRequired by collectLastValue(interactor.isConfirmationRequired)
assertThat(currentPrompt).isNull()
- interactor.useBiometricsForAuthentication(
- info,
- confirmationRequired,
- USER_ID,
- CHALLENGE,
- modalities
- )
+ interactor.useBiometricsForAuthentication(info, USER_ID, CHALLENGE, modalities)
assertThat(currentPrompt).isNotNull()
assertThat(currentPrompt?.title).isEqualTo(TITLE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt
index da55d5a099b7..95b72d554896 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt
@@ -28,7 +28,7 @@ import org.junit.runners.Parameterized.Parameters
@SmallTest
@RunWith(Parameterized::class)
class BoundingBoxOverlapDetectorTest(val testCase: TestCase) : SysuiTestCase() {
- val underTest = BoundingBoxOverlapDetector()
+ val underTest = BoundingBoxOverlapDetector(1f)
@Test
fun isGoodOverlap() {
@@ -83,7 +83,7 @@ private val TOUCH_DATA =
GESTURE_START
)
-private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 500 /* bottom */)
+private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 400 /* bottom */)
private val OVERLAY = Rect(0 /* left */, 100 /* top */, 400 /* right */, 600 /* bottom */)
private fun genTestCases(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
index 689bb0023675..278a43ea1bf1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
@@ -18,7 +18,7 @@ package com.android.systemui.biometrics.ui.viewmodel
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.domain.model.BiometricModality
+import com.android.systemui.biometrics.shared.model.BiometricModality
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -33,6 +33,7 @@ class PromptAuthStateTest : SysuiTestCase() {
with(PromptAuthState(isAuthenticated = false)) {
assertThat(isNotAuthenticated).isTrue()
assertThat(isAuthenticatedAndConfirmed).isFalse()
+ assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
assertThat(isAuthenticatedByFace).isFalse()
assertThat(isAuthenticatedByFingerprint).isFalse()
}
@@ -43,6 +44,7 @@ class PromptAuthStateTest : SysuiTestCase() {
with(PromptAuthState(isAuthenticated = true)) {
assertThat(isNotAuthenticated).isFalse()
assertThat(isAuthenticatedAndConfirmed).isTrue()
+ assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
assertThat(isAuthenticatedByFace).isFalse()
assertThat(isAuthenticatedByFingerprint).isFalse()
}
@@ -50,10 +52,12 @@ class PromptAuthStateTest : SysuiTestCase() {
with(PromptAuthState(isAuthenticated = true, needsUserConfirmation = true)) {
assertThat(isNotAuthenticated).isFalse()
assertThat(isAuthenticatedAndConfirmed).isFalse()
+ assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
assertThat(isAuthenticatedByFace).isFalse()
assertThat(isAuthenticatedByFingerprint).isFalse()
- assertThat(asConfirmed().isAuthenticatedAndConfirmed).isTrue()
+ assertThat(asExplicitlyConfirmed().isAuthenticatedAndConfirmed).isTrue()
+ assertThat(asExplicitlyConfirmed().isAuthenticatedAndExplicitlyConfirmed).isTrue()
}
}
@@ -64,6 +68,7 @@ class PromptAuthStateTest : SysuiTestCase() {
) {
assertThat(isNotAuthenticated).isFalse()
assertThat(isAuthenticatedAndConfirmed).isTrue()
+ assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
assertThat(isAuthenticatedByFace).isTrue()
assertThat(isAuthenticatedByFingerprint).isFalse()
}
@@ -79,6 +84,7 @@ class PromptAuthStateTest : SysuiTestCase() {
) {
assertThat(isNotAuthenticated).isFalse()
assertThat(isAuthenticatedAndConfirmed).isTrue()
+ assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
assertThat(isAuthenticatedByFace).isFalse()
assertThat(isAuthenticatedByFingerprint).isTrue()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 3ba6004e4532..eed7b666fc71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -27,11 +27,14 @@ import com.android.systemui.biometrics.data.repository.FakePromptRepository
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
import com.android.systemui.biometrics.domain.model.BiometricModalities
-import com.android.systemui.biometrics.domain.model.BiometricModality
import com.android.systemui.biometrics.extractAuthenticatorTypes
import com.android.systemui.biometrics.faceSensorPropertiesInternal
import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
+import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
@@ -45,6 +48,9 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
private const val USER_ID = 4
@@ -58,6 +64,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
@Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var vibrator: VibratorHelper
private val testScope = TestScope()
private val promptRepository = FakePromptRepository()
@@ -70,11 +77,11 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
selector = PromptSelectorInteractorImpl(promptRepository, lockPatternUtils)
selector.resetPrompt()
- viewModel = PromptViewModel(selector)
+ viewModel = PromptViewModel(selector, vibrator)
}
@Test
- fun `start idle and show authenticating`() =
+ fun start_idle_and_show_authenticating() =
runGenericTest(doNotStart = true) {
val expectedSize =
if (testCase.shouldStartAsImplicitFlow) PromptSize.SMALL else PromptSize.MEDIUM
@@ -107,7 +114,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
}
@Test
- fun `shows authenticated - no errors`() = runGenericTest {
+ fun shows_authenticated_with_no_errors() = runGenericTest {
// this case can't happen until fingerprint is started
// trigger it now since no error has occurred in this test
val forceError = testCase.isCoex && testCase.authenticatedByFingerprint
@@ -124,6 +131,24 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
)
}
+ @Test
+ fun play_haptic_on_confirm_when_confirmation_required_otherwise_on_authenticated() =
+ runGenericTest {
+ val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+
+ viewModel.showAuthenticated(testCase.authenticatedModality, 1_000L)
+
+ verify(vibrator, if (expectConfirmation) never() else times(1))
+ .vibrateAuthSuccess(any())
+
+ if (expectConfirmation) {
+ viewModel.confirmAuthenticated()
+ }
+
+ verify(vibrator).vibrateAuthSuccess(any())
+ verify(vibrator, never()).vibrateAuthError(any())
+ }
+
private suspend fun TestScope.showAuthenticated(
authenticatedModality: BiometricModality,
expectConfirmation: Boolean,
@@ -172,7 +197,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
}
@Test
- fun `shows temporary errors`() = runGenericTest {
+ fun shows_temporary_errors() = runGenericTest {
val checkAtEnd = suspend { assertButtonsVisible(negative = true) }
showTemporaryErrors(restart = false) { checkAtEnd() }
@@ -180,6 +205,32 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
showTemporaryErrors(restart = true) { checkAtEnd() }
}
+ @Test
+ fun plays_haptic_on_errors() = runGenericTest {
+ viewModel.showTemporaryError(
+ "so sad",
+ messageAfterError = "",
+ authenticateAfterError = false,
+ hapticFeedback = true,
+ )
+
+ verify(vibrator).vibrateAuthError(any())
+ verify(vibrator, never()).vibrateAuthSuccess(any())
+ }
+
+ @Test
+ fun plays_haptic_on_errors_unless_skipped() = runGenericTest {
+ viewModel.showTemporaryError(
+ "still sad",
+ messageAfterError = "",
+ authenticateAfterError = false,
+ hapticFeedback = false,
+ )
+
+ verify(vibrator, never()).vibrateAuthError(any())
+ verify(vibrator, never()).vibrateAuthSuccess(any())
+ }
+
private suspend fun TestScope.showTemporaryErrors(
restart: Boolean,
helpAfterError: String = "",
@@ -233,7 +284,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
}
@Test
- fun `no errors or temporary help after authenticated`() = runGenericTest {
+ fun no_errors_or_temporary_help_after_authenticated() = runGenericTest {
val authenticating by collectLastValue(viewModel.isAuthenticating)
val authenticated by collectLastValue(viewModel.isAuthenticated)
val message by collectLastValue(viewModel.message)
@@ -249,7 +300,13 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
assertThat(canTryAgain).isFalse()
}
- val errorJob = launch { viewModel.showTemporaryError("error") }
+ val errorJob = launch {
+ viewModel.showTemporaryError(
+ "error",
+ messageAfterError = "",
+ authenticateAfterError = false,
+ )
+ }
verifyNoError()
errorJob.join()
verifyNoError()
@@ -268,16 +325,70 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
assertThat(messageIsShowing).isTrue()
}
- // @Test
- fun `suppress errors`() = runGenericTest {
- val errorMessage = "woot"
- val message by collectLastValue(viewModel.message)
+ @Test
+ fun suppress_temporary_error() = runGenericTest {
+ val messages by collectValues(viewModel.message)
+
+ for (error in listOf("never", "see", "me")) {
+ launch {
+ viewModel.showTemporaryError(
+ error,
+ messageAfterError = "or me",
+ authenticateAfterError = false,
+ suppressIf = { _ -> true },
+ )
+ }
+ }
- val errorJob = launch { viewModel.showTemporaryError(errorMessage) }
+ testScheduler.advanceUntilIdle()
+ assertThat(messages).containsExactly(PromptMessage.Empty)
}
@Test
- fun `authenticated at most once`() = runGenericTest {
+ fun suppress_temporary_error_when_already_showing_when_requested() =
+ suppress_temporary_error_when_already_showing(suppress = true)
+
+ @Test
+ fun do_not_suppress_temporary_error_when_already_showing_when_not_requested() =
+ suppress_temporary_error_when_already_showing(suppress = false)
+
+ private fun suppress_temporary_error_when_already_showing(suppress: Boolean) = runGenericTest {
+ val errors = listOf("woot", "oh yeah", "nope")
+ val afterSuffix = "(after)"
+ val expectedErrorMessage = if (suppress) errors.first() else errors.last()
+ val messages by collectValues(viewModel.message)
+
+ for (error in errors) {
+ launch {
+ viewModel.showTemporaryError(
+ error,
+ messageAfterError = "$error $afterSuffix",
+ authenticateAfterError = false,
+ suppressIf = { currentMessage -> suppress && currentMessage.isError },
+ )
+ }
+ }
+
+ testScheduler.runCurrent()
+ assertThat(messages)
+ .containsExactly(
+ PromptMessage.Empty,
+ PromptMessage.Error(expectedErrorMessage),
+ )
+ .inOrder()
+
+ testScheduler.advanceUntilIdle()
+ assertThat(messages)
+ .containsExactly(
+ PromptMessage.Empty,
+ PromptMessage.Error(expectedErrorMessage),
+ PromptMessage.Help("$expectedErrorMessage $afterSuffix"),
+ )
+ .inOrder()
+ }
+
+ @Test
+ fun authenticated_at_most_once() = runGenericTest {
val authenticating by collectLastValue(viewModel.isAuthenticating)
val authenticated by collectLastValue(viewModel.isAuthenticated)
@@ -293,7 +404,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
}
@Test
- fun `authenticating cannot restart after authenticated`() = runGenericTest {
+ fun authenticating_cannot_restart_after_authenticated() = runGenericTest {
val authenticating by collectLastValue(viewModel.isAuthenticating)
val authenticated by collectLastValue(viewModel.isAuthenticated)
@@ -309,7 +420,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
}
@Test
- fun `confirm authentication`() = runGenericTest {
+ fun confirm_authentication() = runGenericTest {
val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
viewModel.showAuthenticated(testCase.authenticatedModality, 0)
@@ -341,7 +452,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
}
@Test
- fun `cannot confirm unless authenticated`() = runGenericTest {
+ fun cannot_confirm_unless_authenticated() = runGenericTest {
val authenticating by collectLastValue(viewModel.isAuthenticating)
val authenticated by collectLastValue(viewModel.isAuthenticated)
@@ -360,7 +471,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
}
@Test
- fun `shows help - before authenticated`() = runGenericTest {
+ fun shows_help_before_authenticated() = runGenericTest {
val helpMessage = "please help yourself to some cookies"
val message by collectLastValue(viewModel.message)
val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
@@ -379,7 +490,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
}
@Test
- fun `shows help - after authenticated`() = runGenericTest {
+ fun shows_help_after_authenticated() = runGenericTest {
val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
val helpMessage = "more cookies please"
val authenticating by collectLastValue(viewModel.isAuthenticating)
@@ -388,6 +499,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
val size by collectLastValue(viewModel.size)
val legacyState by collectLastValue(viewModel.legacyState)
+ val confirmationRequired by collectLastValue(viewModel.isConfirmationRequired)
if (testCase.isCoex && testCase.authenticatedByFingerprint) {
viewModel.ensureFingerprintHasStarted(isDelayed = true)
@@ -396,7 +508,11 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
viewModel.showHelp(helpMessage)
assertThat(size).isEqualTo(PromptSize.MEDIUM)
- assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION)
+ if (confirmationRequired == true) {
+ assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION)
+ } else {
+ assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
+ }
assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
assertThat(messageVisible).isTrue()
assertThat(authenticating).isFalse()
@@ -409,7 +525,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
}
@Test
- fun `retries after failure`() = runGenericTest {
+ fun retries_after_failure() = runGenericTest {
val errorMessage = "bad"
val helpMessage = "again?"
val expectTryAgainButton = testCase.isFaceOnly
@@ -455,7 +571,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
}
@Test
- fun `switch to credential fallback`() = runGenericTest {
+ fun switch_to_credential_fallback() = runGenericTest {
val size by collectLastValue(viewModel.size)
// TODO(b/251476085): remove Spaghetti, migrate logic, and update this test
@@ -631,7 +747,6 @@ private fun PromptSelectorInteractor.initializePrompt(
}
useBiometricsForAuthentication(
info,
- requireConfirmation,
USER_ID,
CHALLENGE,
BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java
new file mode 100644
index 000000000000..9d9b263c5df5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.globalactions;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+
+import android.os.PowerManager;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.BlurUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ShutdownUiTest extends SysuiTestCase {
+
+ ShutdownUi mShutdownUi;
+ @Mock
+ BlurUtils mBlurUtils;
+
+ @Before
+ public void setUp() throws Exception {
+ mShutdownUi = new ShutdownUi(getContext(), mBlurUtils);
+ }
+
+ @Test
+ public void getRebootMessage_update() {
+ int messageId = mShutdownUi.getRebootMessage(true, PowerManager.REBOOT_RECOVERY_UPDATE);
+ assertEquals(messageId, R.string.reboot_to_update_reboot);
+ }
+
+ @Test
+ public void getRebootMessage_rebootDefault() {
+ int messageId = mShutdownUi.getRebootMessage(true, "anything-else");
+ assertEquals(messageId, R.string.reboot_to_reset_message);
+ }
+
+ @Test
+ public void getRebootMessage_shutdown() {
+ int messageId = mShutdownUi.getRebootMessage(false, "anything-else");
+ assertEquals(messageId, R.string.shutdown_progress);
+ }
+
+ @Test
+ public void getReasonMessage_update() {
+ String message = mShutdownUi.getReasonMessage(PowerManager.REBOOT_RECOVERY_UPDATE);
+ assertEquals(message, mContext.getString(R.string.reboot_to_update_title));
+ }
+
+ @Test
+ public void getReasonMessage_rebootDefault() {
+ String message = mShutdownUi.getReasonMessage(PowerManager.REBOOT_RECOVERY);
+ assertEquals(message, mContext.getString(R.string.reboot_to_reset_title));
+ }
+
+ @Test
+ public void getRebootMessage_defaultToNone() {
+ String message = mShutdownUi.getReasonMessage("anything-else");
+ assertNull(message);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 1c8241433172..78654a37a75d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -182,6 +182,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
when(mPowerManager.newWakeLock(anyInt(), any())).thenReturn(mock(WakeLock.class));
+ when(mPowerManager.isInteractive()).thenReturn(true);
when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true);
when(mInteractionJankMonitor.end(anyInt())).thenReturn(true);
final ViewRootImpl testViewRoot = mock(ViewRootImpl.class);
@@ -480,6 +481,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
TestableLooper.get(this).processAllMessages();
assertFalse(mViewMediator.isShowingAndNotOccluded());
+ verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(false);
}
@Test
@@ -496,6 +498,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
TestableLooper.get(this).processAllMessages();
assertTrue(mViewMediator.isShowingAndNotOccluded());
+ verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(true);
}
@Test
@@ -504,6 +507,9 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
startMockKeyguardExitAnimation();
cancelMockKeyguardExitAnimation();
+ // Calling cancel above results in keyguard not visible, as there is no pending lock
+ verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(false);
+
mViewMediator.maybeHandlePendingLock();
TestableLooper.get(this).processAllMessages();
@@ -518,9 +524,15 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
- public void testStartKeyguardExitAnimation_expectSurfaceBehindRemoteAnimation() {
+ public void testStartKeyguardExitAnimation_expectSurfaceBehindRemoteAnimationAndExits() {
startMockKeyguardExitAnimation();
assertTrue(mViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind());
+
+ mViewMediator.mViewMediatorCallback.keyguardDonePending(true,
+ mUpdateMonitor.getCurrentUser());
+ mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
+ TestableLooper.get(this).processAllMessages();
+ verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(false);
}
/**
@@ -703,8 +715,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
when(mKeyguardStateController.isShowing()).thenReturn(true);
TestableLooper.get(this).processAllMessages();
- when(mPowerManager.isInteractive()).thenReturn(true);
-
mViewMediator.onSystemReady();
TestableLooper.get(this).processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 3995e71d24d4..8ef8324d07dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -89,6 +89,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
@Mock private lateinit var shadeController: ShadeController
+ @Mock private lateinit var shadeLogger: ShadeLogger
@Mock private lateinit var ambientState: AmbientState
@Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
@Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
@@ -109,6 +110,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
@Mock
lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel
+ private lateinit var fakeClock: FakeSystemClock
private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
private lateinit var interactionEventHandler: InteractionEventHandler
@@ -147,6 +149,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
),
inputProxy = inputProxy,
)
+ fakeClock = FakeSystemClock()
underTest =
NotificationShadeWindowViewController(
lockscreenShadeTransitionController,
@@ -167,6 +170,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
keyguardUnlockAnimationController,
notificationInsetsController,
ambientState,
+ shadeLogger,
pulsingGestureListener,
keyguardBouncerViewModel,
keyguardBouncerComponentFactory,
@@ -174,7 +178,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
primaryBouncerToGoneTransitionViewModel,
featureFlags,
{ multiShadeInteractor },
- FakeSystemClock(),
+ fakeClock,
{
MultiShadeMotionEventInteractor(
applicationContext = context,
@@ -329,6 +333,33 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
}
@Test
+ fun handleDispatchTouchEvent_launchAnimationRunningTimesOut() =
+ testScope.runTest {
+ // GIVEN touch dispatcher in a state that returns true
+ underTest.setStatusBarViewController(phoneStatusBarViewController)
+ whenever(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()).thenReturn(
+ true
+ )
+ assertThat(interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)).isTrue()
+
+ // WHEN launch animation is running for 2 seconds
+ fakeClock.setUptimeMillis(10000)
+ underTest.setExpandAnimationRunning(true)
+ fakeClock.advanceTime(2000)
+
+ // THEN touch is ignored
+ assertThat(interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)).isFalse()
+
+ // WHEN Launch animation is running for 6 seconds
+ fakeClock.advanceTime(4000)
+
+ // THEN move is ignored, down is handled, and window is notified
+ assertThat(interactionEventHandler.handleDispatchTouchEvent(MOVE_EVENT)).isFalse()
+ assertThat(interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)).isTrue()
+ verify(notificationShadeWindowController).setLaunchingActivity(false)
+ }
+
+ @Test
fun shouldInterceptTouchEvent_statusBarKeyguardViewManagerShouldIntercept() {
// down event should be intercepted by keyguardViewManager
whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT))
@@ -348,6 +379,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
companion object {
private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+ private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
private const val VIEW_BOTTOM = 100
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index fe9574251ddb..f87856b05dc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -99,6 +99,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
@Mock private lateinit var lockIconViewController: LockIconViewController
@Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
@Mock private lateinit var ambientState: AmbientState
+ @Mock private lateinit var shadeLogger: ShadeLogger
@Mock private lateinit var pulsingGestureListener: PulsingGestureListener
@Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
@Mock private lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@@ -179,6 +180,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
keyguardUnlockAnimationController,
notificationInsetsController,
ambientState,
+ shadeLogger,
pulsingGestureListener,
keyguardBouncerViewModel,
keyguardBouncerComponentFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 77026308ad38..28a172894dd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -115,6 +115,7 @@ import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
+import com.android.systemui.log.LogBuffer;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.notetask.NoteTaskController;
import com.android.systemui.plugins.ActivityStarter;
@@ -435,6 +436,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mShadeController = spy(new ShadeControllerImpl(
mCommandQueue,
+ mock(LogBuffer.class),
mKeyguardStateController,
mStatusBarStateController,
mStatusBarKeyguardViewManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index ea534bbd0794..99f9c8ece1e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -132,6 +132,22 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
verify(shadeViewController, times(1)).showAodUi()
}
+ @Test
+ fun testAodUiShowNotInvokedIfWakingUp() {
+ `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(true)
+ `when`(powerManager.isInteractive).thenReturn(false)
+
+ val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+ controller.startAnimation()
+ controller.onStartedWakingUp()
+
+ verify(handler).postDelayed(callbackCaptor.capture(), anyLong())
+
+ callbackCaptor.value.run()
+
+ verify(shadeViewController, never()).showAodUi()
+ }
+
/**
* The AOD UI is shown during the screen off animation, after a delay to allow the light reveal
* animation to start. If the device is woken up during the screen off, we should *never* do
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFaceSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFaceSettingsRepository.kt
new file mode 100644
index 000000000000..af2706e6b287
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFaceSettingsRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.systemui.biometrics.data.repository
+
+import kotlinx.coroutines.flow.flowOf
+
+/** Fake settings for tests. */
+class FakeFaceSettingsRepository : FaceSettingsRepository {
+
+ private val userRepositories = mutableMapOf<Int, FaceUserSettingsRepository>()
+
+ /** Add fixed settings for a user. */
+ fun setUserSettings(userId: Int, alwaysRequireConfirmationInApps: Boolean = false) {
+ userRepositories[userId] =
+ object : FaceUserSettingsRepository {
+ override val userId = userId
+ override val alwaysRequireConfirmationInApps =
+ flowOf(alwaysRequireConfirmationInApps)
+ }
+ }
+
+ override fun forUser(id: Int?) = userRepositories[id] ?: FaceUserSettingsRepositoryImpl.Empty
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
index d270700aa856..42ec8fed0127 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
@@ -31,13 +31,20 @@ class FakePromptRepository : PromptRepository {
userId: Int,
gatekeeperChallenge: Long?,
kind: PromptKind,
- requireConfirmation: Boolean,
+ ) = setPrompt(promptInfo, userId, gatekeeperChallenge, kind, forceConfirmation = false)
+
+ fun setPrompt(
+ promptInfo: PromptInfo,
+ userId: Int,
+ gatekeeperChallenge: Long?,
+ kind: PromptKind,
+ forceConfirmation: Boolean = false,
) {
_promptInfo.value = promptInfo
_userId.value = userId
_challenge.value = gatekeeperChallenge
_kind.value = kind
- _isConfirmationRequired.value = requireConfirmation
+ _isConfirmationRequired.value = promptInfo.isConfirmationRequested || forceConfirmation
}
override fun unsetPrompt() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt
index 1403cea1c625..3fd11a1db96f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt
@@ -26,7 +26,7 @@ class FakeDisplayTracker constructor(val context: Context) : DisplayTracker {
override var defaultDisplayId: Int = Display.DEFAULT_DISPLAY
override var allDisplays: Array<Display> = displayManager.displays
- private val displayCallbacks: MutableList<DisplayTracker.Callback> = ArrayList()
+ val displayCallbacks: MutableList<DisplayTracker.Callback> = ArrayList()
private val brightnessCallbacks: MutableList<DisplayTracker.Callback> = ArrayList()
override fun addDisplayChangeCallback(callback: DisplayTracker.Callback, executor: Executor) {
displayCallbacks.add(callback)
@@ -43,12 +43,12 @@ class FakeDisplayTracker constructor(val context: Context) : DisplayTracker {
brightnessCallbacks.remove(callback)
}
- fun setDefaultDisplay(displayId: Int) {
- defaultDisplayId = displayId
+ override fun getDisplay(displayId: Int): Display {
+ return allDisplays.filter { display -> display.displayId == displayId }[0]
}
- fun setDisplays(displays: Array<Display>) {
- allDisplays = displays
+ fun setDefaultDisplay(displayId: Int) {
+ defaultDisplayId = displayId
}
fun triggerOnDisplayAdded(displayId: Int) {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 71428284882d..893761842a5d 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -73,8 +73,8 @@ import android.content.pm.ProviderInfo;
import android.content.pm.UserInfo;
import android.content.res.ObbInfo;
import android.database.ContentObserver;
-import android.media.MediaCodecList;
import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.net.Uri;
import android.os.BatteryManager;
@@ -219,6 +219,8 @@ class StorageManagerService extends IStorageManager.Stub
@GuardedBy("mLock")
private final Set<Integer> mCeStoragePreparedUsers = new ArraySet<>();
+ private volatile long mInternalStorageSize = 0;
+
public static class Lifecycle extends SystemService {
private StorageManagerService mStorageManagerService;
@@ -3563,6 +3565,15 @@ class StorageManagerService extends IStorageManager.Stub
return authority;
}
+ @Override
+ public long getInternalStorageBlockDeviceSize() throws RemoteException {
+ if (mInternalStorageSize == 0) {
+ mInternalStorageSize = mVold.getStorageSize();
+ }
+
+ return mInternalStorageSize;
+ }
+
/**
* Enforces that the caller is the {@link ExternalStorageService}
*
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 235843643a0d..1a2166d96d06 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -2123,7 +2123,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
synchronized (mUidObserverLock) {
- if (ActivityManager.isProcStateBackground(procState)) {
+ if (procState != ActivityManager.PROCESS_STATE_TOP) {
disableGameMode(uid);
return;
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f313e168c62d..ac11473063d8 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7577,6 +7577,10 @@ public class AudioService extends IAudioService.Stub
throw new IllegalArgumentException("Illegal BluetoothProfile profile for device "
+ previousDevice + " -> " + newDevice + ". Got: " + profile);
}
+
+ sDeviceLogger.enqueue(new EventLogger.StringEvent("BlutoothActiveDeviceChanged for "
+ + BluetoothProfile.getProfileName(profile) + ", device update " + previousDevice
+ + " -> " + newDevice));
AudioDeviceBroker.BtDeviceChangedData data =
new AudioDeviceBroker.BtDeviceChangedData(newDevice, previousDevice, info,
"AudioService");
@@ -9621,6 +9625,9 @@ public class AudioService extends IAudioService.Stub
}
} else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
+ sDeviceLogger.enqueue(new EventLogger.StringEvent(
+ "BluetoothAdapter ACTION_STATE_CHANGED with state " + state));
+
if (state == BluetoothAdapter.STATE_OFF ||
state == BluetoothAdapter.STATE_TURNING_OFF) {
mDeviceBroker.disconnectAllBluetoothProfiles();
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 3560797ce2cf..aaf52927d8a9 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -459,6 +459,8 @@ public class BtHelper {
//@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*package*/ synchronized void onBtProfileDisconnected(int profile) {
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+ "BT profile " + BluetoothProfile.getProfileName(profile) + " disconnected"));
switch (profile) {
case BluetoothProfile.A2DP:
mA2dp = null;
@@ -487,6 +489,9 @@ public class BtHelper {
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) {
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+ "BT profile " + BluetoothProfile.getProfileName(profile) + " connected to proxy "
+ + proxy));
if (profile == BluetoothProfile.HEADSET) {
onHeadsetProfileConnected((BluetoothHeadset) proxy);
return;
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 8ef2a1bd26c2..2ae3118d7bfa 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -21,7 +21,10 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRIN
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE;
+import static com.android.server.biometrics.BiometricSensor.STATE_CANCELING;
+import static com.android.server.biometrics.BiometricSensor.STATE_UNKNOWN;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_CALLED;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE;
@@ -147,6 +150,10 @@ public final class AuthSession implements IBinder.DeathRecipient {
// Timestamp when hardware authentication occurred
private long mAuthenticatedTimeMs;
+ @NonNull
+ private final OperationContextExt mOperationContext;
+
+
AuthSession(@NonNull Context context,
@NonNull BiometricContext biometricContext,
@NonNull IStatusBarService statusBarService,
@@ -212,6 +219,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
mFingerprintSensorProperties = fingerprintSensorProperties;
mCancelled = false;
mBiometricFrameworkStatsLogger = logger;
+ mOperationContext = new OperationContextExt(true /* isBP */);
try {
mClientReceiver.asBinder().linkToDeath(this, 0 /* flags */);
@@ -439,6 +447,13 @@ public final class AuthSession implements IBinder.DeathRecipient {
return false;
}
+ final boolean errorLockout = error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
+ || error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
+ if (errorLockout) {
+ cancelAllSensors(sensor -> Utils.isAtLeastStrength(sensorIdToStrength(sensorId),
+ sensor.getCurrentStrength()));
+ }
+
mErrorEscrow = error;
mVendorCodeEscrow = vendorCode;
@@ -477,8 +492,6 @@ public final class AuthSession implements IBinder.DeathRecipient {
case STATE_AUTH_STARTED:
case STATE_AUTH_STARTED_UI_SHOWING: {
- final boolean errorLockout = error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
- || error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
if (isAllowDeviceCredential() && errorLockout) {
// SystemUI handles transition from biometric to device credential.
mState = STATE_SHOWING_DEVICE_CREDENTIAL;
@@ -573,6 +586,8 @@ public final class AuthSession implements IBinder.DeathRecipient {
} else {
Slog.d(TAG, "delaying fingerprint sensor start");
}
+
+ mBiometricContext.updateContext(mOperationContext, isCrypto());
}
// call once anytime after onDialogAnimatedIn() to indicate it's appropriate to start the
@@ -675,7 +690,9 @@ public final class AuthSession implements IBinder.DeathRecipient {
}
private boolean pauseSensorIfSupported(int sensorId) {
- if (sensorIdToModality(sensorId) == TYPE_FACE) {
+ boolean isSensorCancelling = sensorIdToState(sensorId) == STATE_CANCELING;
+ // If the sensor is locked out, canceling sensors operation is handled in onErrorReceived()
+ if (sensorIdToModality(sensorId) == TYPE_FACE && !isSensorCancelling) {
cancelAllSensors(sensor -> sensor.id == sensorId);
return true;
}
@@ -733,12 +750,12 @@ public final class AuthSession implements IBinder.DeathRecipient {
+ ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
+ ", RequireConfirmation: " + mPreAuthInfo.confirmationRequested
+ ", State: " + FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED
- + ", Latency: " + latency);
+ + ", Latency: " + latency
+ + ", SessionId: " + mOperationContext.getId());
}
mBiometricFrameworkStatsLogger.authenticate(
- mBiometricContext.updateContext(new OperationContextExt(true /* isBP */),
- isCrypto()),
+ mOperationContext,
statsModality(),
BiometricsProtoEnums.ACTION_UNKNOWN,
BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
@@ -770,13 +787,13 @@ public final class AuthSession implements IBinder.DeathRecipient {
+ ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
+ ", Reason: " + reason
+ ", Error: " + error
- + ", Latency: " + latency);
+ + ", Latency: " + latency
+ + ", SessionId: " + mOperationContext.getId());
}
// Auth canceled
if (error != 0) {
mBiometricFrameworkStatsLogger.error(
- mBiometricContext.updateContext(new OperationContextExt(true /* isBP */),
- isCrypto()),
+ mOperationContext,
statsModality(),
BiometricsProtoEnums.ACTION_AUTHENTICATE,
BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
@@ -948,6 +965,27 @@ public final class AuthSession implements IBinder.DeathRecipient {
return TYPE_NONE;
}
+ private @BiometricSensor.SensorState int sensorIdToState(int sensorId) {
+ for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
+ if (sensorId == sensor.id) {
+ return sensor.getSensorState();
+ }
+ }
+ Slog.e(TAG, "Unknown sensor: " + sensorId);
+ return STATE_UNKNOWN;
+ }
+
+ @BiometricManager.Authenticators.Types
+ private int sensorIdToStrength(int sensorId) {
+ for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
+ if (sensorId == sensor.id) {
+ return sensor.getCurrentStrength();
+ }
+ }
+ Slog.e(TAG, "Unknown sensor: " + sensorId);
+ return BIOMETRIC_CONVENIENCE;
+ }
+
private String getAcquiredMessageForSensor(int sensorId, int acquiredInfo, int vendorCode) {
final @Modality int modality = sensorIdToModality(sensorId);
switch (modality) {
diff --git a/services/core/java/com/android/server/biometrics/BiometricCameraManager.java b/services/core/java/com/android/server/biometrics/BiometricCameraManager.java
new file mode 100644
index 000000000000..058ea6bbb696
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricCameraManager.java
@@ -0,0 +1,32 @@
+/*
+ * 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.server.biometrics;
+
+/**
+ * Interface for biometrics to get camera status.
+ */
+public interface BiometricCameraManager {
+ /**
+ * Returns true if any camera is in use.
+ */
+ boolean isAnyCameraUnavailable();
+
+ /**
+ * Returns true if privacy is enabled and camera access is disabled.
+ */
+ boolean isCameraPrivacyEnabled();
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java b/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java
new file mode 100644
index 000000000000..000ee5446962
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+
+import android.annotation.NonNull;
+import android.hardware.SensorPrivacyManager;
+import android.hardware.camera2.CameraManager;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+public class BiometricCameraManagerImpl implements BiometricCameraManager {
+
+ private final CameraManager mCameraManager;
+ private final SensorPrivacyManager mSensorPrivacyManager;
+ private final ConcurrentHashMap<String, Boolean> mIsCameraAvailable = new ConcurrentHashMap<>();
+
+ private final CameraManager.AvailabilityCallback mCameraAvailabilityCallback =
+ new CameraManager.AvailabilityCallback() {
+ @Override
+ public void onCameraAvailable(@NonNull String cameraId) {
+ mIsCameraAvailable.put(cameraId, true);
+ }
+
+ @Override
+ public void onCameraUnavailable(@NonNull String cameraId) {
+ mIsCameraAvailable.put(cameraId, false);
+ }
+ };
+
+ public BiometricCameraManagerImpl(@NonNull CameraManager cameraManager,
+ @NonNull SensorPrivacyManager sensorPrivacyManager) {
+ mCameraManager = cameraManager;
+ mSensorPrivacyManager = sensorPrivacyManager;
+ mCameraManager.registerAvailabilityCallback(mCameraAvailabilityCallback, null);
+ }
+
+ @Override
+ public boolean isAnyCameraUnavailable() {
+ for (String cameraId : mIsCameraAvailable.keySet()) {
+ if (!mIsCameraAvailable.get(cameraId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isCameraPrivacyEnabled() {
+ return mSensorPrivacyManager != null && mSensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, CAMERA);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 0942d8527565..279aaf9d3253 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -17,6 +17,8 @@
package com.android.server.biometrics;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE;
@@ -33,6 +35,7 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
+import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
@@ -47,6 +50,7 @@ import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorPropertiesInternal;
+import android.hardware.camera2.CameraManager;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.net.Uri;
@@ -124,6 +128,8 @@ public class BiometricService extends SystemService {
AuthSession mAuthSession;
private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final BiometricCameraManager mBiometricCameraManager;
+
/**
* Tracks authenticatorId invalidation. For more details, see
* {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}.
@@ -365,7 +371,7 @@ public class BiometricService extends SystemService {
public boolean getConfirmationAlwaysRequired(@BiometricAuthenticator.Modality int modality,
int userId) {
switch (modality) {
- case BiometricAuthenticator.TYPE_FACE:
+ case TYPE_FACE:
if (!mFaceAlwaysRequireConfirmation.containsKey(userId)) {
onChange(true /* selfChange */,
FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
@@ -563,22 +569,6 @@ public class BiometricService extends SystemService {
Utils.combineAuthenticatorBundles(promptInfo);
- // Set the default title if necessary.
- if (promptInfo.isUseDefaultTitle()) {
- if (TextUtils.isEmpty(promptInfo.getTitle())) {
- promptInfo.setTitle(getContext()
- .getString(R.string.biometric_dialog_default_title));
- }
- }
-
- // Set the default subtitle if necessary.
- if (promptInfo.isUseDefaultSubtitle()) {
- if (TextUtils.isEmpty(promptInfo.getSubtitle())) {
- promptInfo.setSubtitle(getContext()
- .getString(R.string.biometric_dialog_default_subtitle));
- }
- }
-
final long requestId = mRequestCounter.get();
mHandler.post(() -> handleAuthenticate(
token, requestId, operationId, userId, receiver, opPackageName, promptInfo));
@@ -933,7 +923,7 @@ public class BiometricService extends SystemService {
return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors,
userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */,
- getContext());
+ getContext(), mBiometricCameraManager);
}
/**
@@ -1026,6 +1016,11 @@ public class BiometricService extends SystemService {
public UserManager getUserManager(Context context) {
return context.getSystemService(UserManager.class);
}
+
+ public BiometricCameraManager getBiometricCameraManager(Context context) {
+ return new BiometricCameraManagerImpl(context.getSystemService(CameraManager.class),
+ context.getSystemService(SensorPrivacyManager.class));
+ }
}
/**
@@ -1054,6 +1049,7 @@ public class BiometricService extends SystemService {
mRequestCounter = mInjector.getRequestGenerator();
mBiometricContext = injector.getBiometricContext(context);
mUserManager = injector.getUserManager(context);
+ mBiometricCameraManager = injector.getBiometricCameraManager(context);
try {
injector.getActivityManagerService().registerUserSwitchObserver(
@@ -1290,7 +1286,34 @@ public class BiometricService extends SystemService {
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager,
mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo,
opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(),
- getContext());
+ getContext(), mBiometricCameraManager);
+
+ // Set the default title if necessary.
+ if (promptInfo.isUseDefaultTitle()) {
+ if (TextUtils.isEmpty(promptInfo.getTitle())) {
+ promptInfo.setTitle(getContext()
+ .getString(R.string.biometric_dialog_default_title));
+ }
+ }
+
+ final int eligible = preAuthInfo.getEligibleModalities();
+ final boolean hasEligibleFingerprintSensor =
+ (eligible & TYPE_FINGERPRINT) == TYPE_FINGERPRINT;
+ final boolean hasEligibleFaceSensor = (eligible & TYPE_FACE) == TYPE_FACE;
+
+ // Set the subtitle according to the modality.
+ if (promptInfo.isUseDefaultSubtitle()) {
+ if (hasEligibleFingerprintSensor && hasEligibleFaceSensor) {
+ promptInfo.setSubtitle(getContext()
+ .getString(R.string.biometric_dialog_default_subtitle));
+ } else if (hasEligibleFingerprintSensor) {
+ promptInfo.setSubtitle(getContext()
+ .getString(R.string.biometric_dialog_fingerprint_subtitle));
+ } else if (hasEligibleFaceSensor) {
+ promptInfo.setSubtitle(getContext()
+ .getString(R.string.biometric_dialog_face_subtitle));
+ }
+ }
final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus();
@@ -1300,9 +1323,7 @@ public class BiometricService extends SystemService {
+ promptInfo.isIgnoreEnrollmentState());
// BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED is added so that BiometricPrompt can
// be shown for this case.
- if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS
- || preAuthStatus.second
- == BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED) {
+ if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) {
// If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but
// CREDENTIAL is requested and available, set the bundle to only request
// CREDENTIAL.
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index 3813fd1971a6..b1740a780539 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -27,7 +27,6 @@ import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustManager;
import android.content.Context;
-import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.PromptInfo;
@@ -73,13 +72,16 @@ class PreAuthInfo {
final Context context;
private final boolean mBiometricRequested;
private final int mBiometricStrengthRequested;
+ private final BiometricCameraManager mBiometricCameraManager;
+
private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
boolean credentialRequested, List<BiometricSensor> eligibleSensors,
List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
boolean confirmationRequested, boolean ignoreEnrollmentState, int userId,
- Context context) {
+ Context context, BiometricCameraManager biometricCameraManager) {
mBiometricRequested = biometricRequested;
mBiometricStrengthRequested = biometricStrengthRequested;
+ mBiometricCameraManager = biometricCameraManager;
this.credentialRequested = credentialRequested;
this.eligibleSensors = eligibleSensors;
@@ -96,7 +98,8 @@ class PreAuthInfo {
BiometricService.SettingObserver settingObserver,
List<BiometricSensor> sensors,
int userId, PromptInfo promptInfo, String opPackageName,
- boolean checkDevicePolicyManager, Context context)
+ boolean checkDevicePolicyManager, Context context,
+ BiometricCameraManager biometricCameraManager)
throws RemoteException {
final boolean confirmationRequested = promptInfo.isConfirmationRequested();
@@ -124,7 +127,7 @@ class PreAuthInfo {
checkDevicePolicyManager, requestedStrength,
promptInfo.getAllowedSensorIds(),
promptInfo.isIgnoreEnrollmentState(),
- context);
+ biometricCameraManager);
Slog.d(TAG, "Package: " + opPackageName
+ " Sensor ID: " + sensor.id
@@ -138,7 +141,7 @@ class PreAuthInfo {
//
// Note: if only a certain sensor is required and the privacy is enabled,
// canAuthenticate() will return false.
- if (status == AUTHENTICATOR_OK || status == BIOMETRIC_SENSOR_PRIVACY_ENABLED) {
+ if (status == AUTHENTICATOR_OK) {
eligibleSensors.add(sensor);
} else {
ineligibleSensors.add(new Pair<>(sensor, status));
@@ -148,7 +151,7 @@ class PreAuthInfo {
return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested,
eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested,
- promptInfo.isIgnoreEnrollmentState(), userId, context);
+ promptInfo.isIgnoreEnrollmentState(), userId, context, biometricCameraManager);
}
/**
@@ -165,12 +168,16 @@ class PreAuthInfo {
BiometricSensor sensor, int userId, String opPackageName,
boolean checkDevicePolicyManager, int requestedStrength,
@NonNull List<Integer> requestedSensorIds,
- boolean ignoreEnrollmentState, Context context) {
+ boolean ignoreEnrollmentState, BiometricCameraManager biometricCameraManager) {
if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) {
return BIOMETRIC_NO_HARDWARE;
}
+ if (sensor.modality == TYPE_FACE && biometricCameraManager.isAnyCameraUnavailable()) {
+ return BIOMETRIC_HARDWARE_NOT_DETECTED;
+ }
+
final boolean wasStrongEnough =
Utils.isAtLeastStrength(sensor.oemStrength, requestedStrength);
final boolean isStrongEnough =
@@ -191,12 +198,10 @@ class PreAuthInfo {
&& !ignoreEnrollmentState) {
return BIOMETRIC_NOT_ENROLLED;
}
- final SensorPrivacyManager sensorPrivacyManager = context
- .getSystemService(SensorPrivacyManager.class);
- if (sensorPrivacyManager != null && sensor.modality == TYPE_FACE) {
- if (sensorPrivacyManager
- .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId)) {
+ if (biometricCameraManager != null && sensor.modality == TYPE_FACE) {
+ if (biometricCameraManager.isCameraPrivacyEnabled()) {
+ //Camera privacy is enabled as the access is disabled
return BIOMETRIC_SENSOR_PRIVACY_ENABLED;
}
}
@@ -266,17 +271,30 @@ class PreAuthInfo {
}
private Pair<BiometricSensor, Integer> calculateErrorByPriority() {
- // If the caller requested STRONG, and the device contains both STRONG and non-STRONG
- // sensors, prioritize BIOMETRIC_NOT_ENROLLED over the weak sensor's
- // BIOMETRIC_INSUFFICIENT_STRENGTH error. Pretty sure we can always prioritize
- // BIOMETRIC_NOT_ENROLLED over any other error (unless of course its calculation is
- // wrong, in which case we should fix that instead).
+ Pair<BiometricSensor, Integer> sensorNotEnrolled = null;
+ Pair<BiometricSensor, Integer> sensorLockout = null;
for (Pair<BiometricSensor, Integer> pair : ineligibleSensors) {
+ int status = pair.second;
+ if (status == BIOMETRIC_LOCKOUT_TIMED || status == BIOMETRIC_LOCKOUT_PERMANENT) {
+ sensorLockout = pair;
+ }
if (pair.second == BIOMETRIC_NOT_ENROLLED) {
- return pair;
+ sensorNotEnrolled = pair;
}
}
+ // If there is a sensor locked out, prioritize lockout over other sensor's error.
+ // See b/286923477.
+ if (sensorLockout != null) {
+ return sensorLockout;
+ }
+
+ // If the caller requested STRONG, and the device contains both STRONG and non-STRONG
+ // sensors, prioritize BIOMETRIC_NOT_ENROLLED over the weak sensor's
+ // BIOMETRIC_INSUFFICIENT_STRENGTH error.
+ if (sensorNotEnrolled != null) {
+ return sensorNotEnrolled;
+ }
return ineligibleSensors.get(0);
}
@@ -292,13 +310,9 @@ class PreAuthInfo {
@AuthenticatorStatus final int status;
@BiometricAuthenticator.Modality int modality = TYPE_NONE;
- final SensorPrivacyManager sensorPrivacyManager = context
- .getSystemService(SensorPrivacyManager.class);
-
boolean cameraPrivacyEnabled = false;
- if (sensorPrivacyManager != null) {
- cameraPrivacyEnabled = sensorPrivacyManager
- .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId);
+ if (mBiometricCameraManager != null) {
+ cameraPrivacyEnabled = mBiometricCameraManager.isCameraPrivacyEnabled();
}
if (mBiometricRequested && credentialRequested) {
@@ -315,7 +329,7 @@ class PreAuthInfo {
// and the face sensor privacy is enabled then return
// BIOMETRIC_SENSOR_PRIVACY_ENABLED.
//
- // Note: This sensor will still be eligible for calls to authenticate.
+ // Note: This sensor will not be eligible for calls to authenticate.
status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
} else {
status = AUTHENTICATOR_OK;
@@ -340,7 +354,7 @@ class PreAuthInfo {
// If the only modality requested is face and the privacy is enabled
// then return BIOMETRIC_SENSOR_PRIVACY_ENABLED.
//
- // Note: This sensor will still be eligible for calls to authenticate.
+ // Note: This sensor will not be eligible for calls to authenticate.
status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
} else {
status = AUTHENTICATOR_OK;
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index 46c77e8a82f2..aa6a0f1bb55f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -210,4 +210,8 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement
public boolean isInterruptable() {
return true;
}
+
+ public boolean isAlreadyCancelled() {
+ return mAlreadyCancelled;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 05ca6e4554fb..6ac163121d8c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -266,8 +266,12 @@ public abstract class AuthenticationClient<T, O extends AuthenticateOptions>
}
} else {
if (isBackgroundAuth) {
- Slog.e(TAG, "cancelling due to background auth");
- cancel();
+ Slog.e(TAG, "Sending cancel to client(Due to background auth)");
+ if (mTaskStackListener != null) {
+ mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+ }
+ sendCancelOnly(getListener());
+ mCallback.onClientFinished(this, false);
} else {
// Allow system-defined limit of number of attempts before giving up
if (mShouldUseLockoutTracker) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 7fa4d6ce4d49..78c38089e803 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -268,6 +268,14 @@ public class BiometricScheduler {
return;
}
+ if (mCurrentOperation.isAcquisitionOperation()) {
+ AcquisitionClient client = (AcquisitionClient) mCurrentOperation.getClientMonitor();
+ if (client.isAlreadyCancelled()) {
+ mCurrentOperation.cancel(mHandler, mInternalCallback);
+ return;
+ }
+ }
+
if (mGestureAvailabilityDispatcher != null && mCurrentOperation.isAcquisitionOperation()) {
mGestureAvailabilityDispatcher.markSensorActive(
mCurrentOperation.getSensorId(), true /* active */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
index 2e1a363bcc68..1a682a9ffefa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
@@ -170,6 +170,12 @@ public class ClientMonitorCallbackConverter {
}
}
+ public void onUdfpsOverlayShown() throws RemoteException {
+ if (mFingerprintServiceReceiver != null) {
+ mFingerprintServiceReceiver.onUdfpsOverlayShown();
+ }
+ }
+
// Face-specific callbacks for FaceManager only
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 7ae31b2a114d..3fce3cc99e8f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -43,7 +43,6 @@ import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.OperationContextExt;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationClient;
-import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
@@ -240,9 +239,6 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut
vendorCode,
getTargetUserId()));
- if (error == BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL) {
- BiometricNotificationUtils.showReEnrollmentNotification(getContext());
- }
super.onError(error, vendorCode);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index ece35c522ec7..3d6a156d6022 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -929,17 +929,19 @@ public class FingerprintService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
- public void onUiReady(long requestId, int sensorId) {
- super.onUiReady_enforcePermission();
+ public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event, long requestId,
+ int sensorId) {
+ super.onUdfpsUiEvent_enforcePermission();
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
- Slog.w(TAG, "No matching provider for onUiReady, sensorId: " + sensorId);
+ Slog.w(TAG, "No matching provider for onUdfpsUiEvent, sensorId: " + sensorId);
return;
}
- provider.onUiReady(requestId, sensorId);
+ provider.onUdfpsUiEvent(event, requestId, sensorId);
}
+
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 004af2c2ad62..26701c110c39 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -130,7 +130,7 @@ public interface ServiceProvider extends
void onPointerUp(long requestId, int sensorId, PointerContext pc);
- void onUiReady(long requestId, int sensorId);
+ void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event, long requestId, int sensorId);
void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
index da7163a2a678..dce0175ca593 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
@@ -17,6 +17,7 @@
package com.android.server.biometrics.sensors.fingerprint;
import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.fingerprint.FingerprintManager;
import com.android.server.biometrics.sensors.BaseClientMonitor;
@@ -28,6 +29,6 @@ import com.android.server.biometrics.sensors.BaseClientMonitor;
public interface Udfps {
void onPointerDown(PointerContext pc);
void onPointerUp(PointerContext pc);
- void onUiReady();
+ void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event);
boolean isPointerDown();
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index 135eccf9026a..ec1eeb138505 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -114,6 +114,11 @@ class BiometricTestSessionImpl extends ITestSession.Stub {
public void onUdfpsPointerUp(int sensorId) {
}
+
+ @Override
+ public void onUdfpsOverlayShown() {
+
+ }
};
BiometricTestSessionImpl(@NonNull Context context, int sensorId,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 2bfc2391b948..3fc36b6df92d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -27,6 +27,7 @@ import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
+import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlay;
@@ -363,9 +364,11 @@ class FingerprintAuthenticationClient
}
@Override
- public void onUiReady() {
+ public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
try {
- getFreshDaemon().getSession().onUiReady();
+ if (event == FingerprintManager.UDFPS_UI_READY) {
+ getFreshDaemon().getSession().onUiReady();
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index c2ca78e91fe3..d35469c4655c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -265,11 +265,20 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps
}
@Override
- public void onUiReady() {
+ public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
try {
- getFreshDaemon().getSession().onUiReady();
+ switch (event) {
+ case FingerprintManager.UDFPS_UI_OVERLAY_SHOWN:
+ getListener().onUdfpsOverlayShown();
+ break;
+ case FingerprintManager.UDFPS_UI_READY:
+ getFreshDaemon().getSession().onUiReady();
+ break;
+ default:
+ Slog.w(TAG, "No matching event for onUdfpsUiEvent");
+ }
} catch (RemoteException e) {
- Slog.e(TAG, "Unable to send UI ready", e);
+ Slog.e(TAG, "Unable to send onUdfpsUiEvent", e);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 9b2ea1589275..e682fe71b53c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -681,14 +681,15 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
}
@Override
- public void onUiReady(long requestId, int sensorId) {
+ public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event, long requestId,
+ int sensorId) {
mFingerprintSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(
requestId, (client) -> {
if (!(client instanceof Udfps)) {
- Slog.e(getTag(), "onUiReady received during client: " + client);
+ Slog.e(getTag(), "onUdfpsUiEvent received during client: " + client);
return;
}
- ((Udfps) client).onUiReady();
+ ((Udfps) client).onUdfpsUiEvent(event);
});
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
index 86a9f7998398..c20a9eb958c4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
@@ -115,6 +115,11 @@ public class BiometricTestSessionImpl extends ITestSession.Stub {
public void onUdfpsPointerUp(int sensorId) {
}
+
+ @Override
+ public void onUdfpsOverlayShown() {
+
+ }
};
BiometricTestSessionImpl(@NonNull Context context, int sensorId,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 9e6f4e4f13f3..1cbbf89e052a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -829,13 +829,14 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
}
@Override
- public void onUiReady(long requestId, int sensorId) {
+ public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event, long requestId,
+ int sensorId) {
mScheduler.getCurrentClientIfMatches(requestId, (client) -> {
if (!(client instanceof Udfps)) {
- Slog.w(TAG, "onUiReady received during client: " + client);
+ Slog.w(TAG, "onUdfpsUiEvent received during client: " + client);
return;
}
- ((Udfps) client).onUiReady();
+ ((Udfps) client).onUdfpsUiEvent(event);
});
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index d22aef8b3971..2a6233824c2e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -27,6 +27,7 @@ import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
+import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlay;
@@ -273,7 +274,7 @@ class FingerprintAuthenticationClient
}
@Override
- public void onUiReady() {
+ public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
// Unsupported in HIDL.
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 362c820b9e8d..ed0a2015a461 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -25,6 +25,7 @@ import android.hardware.biometrics.BiometricOverlayConstants;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
+import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
@@ -130,7 +131,7 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint>
}
@Override
- public void onUiReady() {
+ public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
// Unsupported in HIDL.
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index 78039ef99986..c2b7944eac35 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -186,7 +186,7 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
}
@Override
- public void onUiReady() {
+ public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
// Unsupported in HIDL.
}
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 75709fbb365a..d647757442e0 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -223,11 +223,11 @@ public class AutomaticBrightnessController {
private final ShortTermModel mShortTermModel;
private final ShortTermModel mPausedShortTermModel;
- // Controls High Brightness Mode.
- private HighBrightnessModeController mHbmController;
+ // Controls Brightness range (including High Brightness Mode).
+ private final BrightnessRangeController mBrightnessRangeController;
// Throttles (caps) maximum allowed brightness
- private BrightnessThrottler mBrightnessThrottler;
+ private final BrightnessThrottler mBrightnessThrottler;
private boolean mIsBrightnessThrottled;
// Context-sensitive brightness configurations require keeping track of the foreground app's
@@ -257,7 +257,8 @@ public class AutomaticBrightnessController {
HysteresisLevels screenBrightnessThresholds,
HysteresisLevels ambientBrightnessThresholdsIdle,
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
- HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
+ BrightnessRangeController brightnessModeController,
+ BrightnessThrottler brightnessThrottler,
BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
int ambientLightHorizonLong, float userLux, float userBrightness) {
this(new Injector(), callbacks, looper, sensorManager, lightSensor,
@@ -267,7 +268,7 @@ public class AutomaticBrightnessController {
darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig,
ambientBrightnessThresholds, screenBrightnessThresholds,
ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, context,
- hbmController, brightnessThrottler, idleModeBrightnessMapper,
+ brightnessModeController, brightnessThrottler, idleModeBrightnessMapper,
ambientLightHorizonShort, ambientLightHorizonLong, userLux, userBrightness
);
}
@@ -283,7 +284,8 @@ public class AutomaticBrightnessController {
HysteresisLevels screenBrightnessThresholds,
HysteresisLevels ambientBrightnessThresholdsIdle,
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
- HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
+ BrightnessRangeController brightnessModeController,
+ BrightnessThrottler brightnessThrottler,
BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
int ambientLightHorizonLong, float userLux, float userBrightness) {
mInjector = injector;
@@ -326,7 +328,7 @@ public class AutomaticBrightnessController {
mPendingForegroundAppPackageName = null;
mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
- mHbmController = hbmController;
+ mBrightnessRangeController = brightnessModeController;
mBrightnessThrottler = brightnessThrottler;
mInteractiveModeBrightnessMapper = interactiveModeBrightnessMapper;
mIdleModeBrightnessMapper = idleModeBrightnessMapper;
@@ -607,10 +609,11 @@ public class AutomaticBrightnessController {
pw.println();
pw.println(" mInteractiveMapper=");
- mInteractiveModeBrightnessMapper.dump(pw, mHbmController.getNormalBrightnessMax());
+ mInteractiveModeBrightnessMapper.dump(pw,
+ mBrightnessRangeController.getNormalBrightnessMax());
if (mIdleModeBrightnessMapper != null) {
pw.println(" mIdleMapper=");
- mIdleModeBrightnessMapper.dump(pw, mHbmController.getNormalBrightnessMax());
+ mIdleModeBrightnessMapper.dump(pw, mBrightnessRangeController.getNormalBrightnessMax());
}
pw.println();
@@ -736,7 +739,7 @@ public class AutomaticBrightnessController {
mAmbientDarkeningThreshold =
mAmbientBrightnessThresholds.getDarkeningThreshold(lux);
}
- mHbmController.onAmbientLuxChange(mAmbientLux);
+ mBrightnessRangeController.onAmbientLuxChange(mAmbientLux);
// If the short term model was invalidated and the change is drastic enough, reset it.
@@ -976,9 +979,9 @@ public class AutomaticBrightnessController {
// Clamps values with float range [0.0-1.0]
private float clampScreenBrightness(float value) {
- final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(),
+ final float minBrightness = Math.min(mBrightnessRangeController.getCurrentBrightnessMin(),
mBrightnessThrottler.getBrightnessCap());
- final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(),
+ final float maxBrightness = Math.min(mBrightnessRangeController.getCurrentBrightnessMax(),
mBrightnessThrottler.getBrightnessCap());
return MathUtils.constrain(value, minBrightness, maxBrightness);
}
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
new file mode 100644
index 000000000000..5ca296153783
--- /dev/null
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -0,0 +1,128 @@
+/*
+ * 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.server.display;
+
+import android.hardware.display.BrightnessInfo;
+import android.os.IBinder;
+
+import java.io.PrintWriter;
+import java.util.function.BooleanSupplier;
+
+class BrightnessRangeController {
+
+ private static final boolean NBM_FEATURE_FLAG = true;
+
+ private final HighBrightnessModeController mHbmController;
+ private final NormalBrightnessModeController mNormalBrightnessModeController =
+ new NormalBrightnessModeController();
+
+ private final Runnable mModeChangeCallback;
+
+ BrightnessRangeController(HighBrightnessModeController hbmController,
+ Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig) {
+ mHbmController = hbmController;
+ mModeChangeCallback = modeChangeCallback;
+ mNormalBrightnessModeController.resetNbmData(displayDeviceConfig.getLuxThrottlingData());
+ }
+
+
+ void dump(PrintWriter pw) {
+ mHbmController.dump(pw);
+ mNormalBrightnessModeController.dump(pw);
+ }
+
+ void onAmbientLuxChange(float ambientLux) {
+ applyChanges(
+ () -> mNormalBrightnessModeController.onAmbientLuxChange(ambientLux),
+ () -> mHbmController.onAmbientLuxChange(ambientLux)
+ );
+ }
+
+ float getNormalBrightnessMax() {
+ return mHbmController.getNormalBrightnessMax();
+ }
+
+ void loadFromConfig(HighBrightnessModeMetadata hbmMetadata, IBinder token,
+ DisplayDeviceInfo info, DisplayDeviceConfig displayDeviceConfig) {
+ applyChanges(
+ () -> mNormalBrightnessModeController.resetNbmData(
+ displayDeviceConfig.getLuxThrottlingData()),
+ () -> {
+ mHbmController.setHighBrightnessModeMetadata(hbmMetadata);
+ mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
+ displayDeviceConfig.getHighBrightnessModeData(),
+ displayDeviceConfig::getHdrBrightnessFromSdr);
+ }
+ );
+ }
+
+ void stop() {
+ mHbmController.stop();
+ }
+
+ void setAutoBrightnessEnabled(int state) {
+ applyChanges(
+ () -> mNormalBrightnessModeController.setAutoBrightnessState(state),
+ () -> mHbmController.setAutoBrightnessEnabled(state)
+ );
+ }
+
+ void onBrightnessChanged(float brightness, float unthrottledBrightness,
+ @BrightnessInfo.BrightnessMaxReason int throttlingReason) {
+ mHbmController.onBrightnessChanged(brightness, unthrottledBrightness, throttlingReason);
+ }
+
+ float getCurrentBrightnessMin() {
+ return mHbmController.getCurrentBrightnessMin();
+ }
+
+
+ float getCurrentBrightnessMax() {
+ if (NBM_FEATURE_FLAG && mHbmController.getHighBrightnessMode()
+ == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF) {
+ return Math.min(mHbmController.getCurrentBrightnessMax(),
+ mNormalBrightnessModeController.getCurrentBrightnessMax());
+ }
+ return mHbmController.getCurrentBrightnessMax();
+ }
+
+ int getHighBrightnessMode() {
+ return mHbmController.getHighBrightnessMode();
+ }
+
+ float getHdrBrightnessValue() {
+ return mHbmController.getHdrBrightnessValue();
+ }
+
+ float getTransitionPoint() {
+ return mHbmController.getTransitionPoint();
+ }
+
+ private void applyChanges(BooleanSupplier nbmChangesFunc, Runnable hbmChangesFunc) {
+ if (NBM_FEATURE_FLAG) {
+ boolean nbmTransitionChanged = nbmChangesFunc.getAsBoolean();
+ hbmChangesFunc.run();
+ // if nbm transition changed - trigger callback
+ // HighBrightnessModeController handles sending changes itself
+ if (nbmTransitionChanged) {
+ mModeChangeCallback.run();
+ }
+ } else {
+ hbmChangesFunc.run();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 7a797dd2250c..2c67c433d06c 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -41,6 +41,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.server.display.config.AutoBrightness;
import com.android.server.display.config.BlockingZoneConfig;
+import com.android.server.display.config.BrightnessLimitMap;
import com.android.server.display.config.BrightnessThresholds;
import com.android.server.display.config.BrightnessThrottlingMap;
import com.android.server.display.config.BrightnessThrottlingPoint;
@@ -51,8 +52,11 @@ import com.android.server.display.config.DisplayQuirks;
import com.android.server.display.config.HbmTiming;
import com.android.server.display.config.HighBrightnessMode;
import com.android.server.display.config.IntegerArray;
+import com.android.server.display.config.LuxThrottling;
import com.android.server.display.config.NitsMap;
+import com.android.server.display.config.NonNegativeFloatToFloatPoint;
import com.android.server.display.config.Point;
+import com.android.server.display.config.PredefinedBrightnessLimitNames;
import com.android.server.display.config.RefreshRateConfigs;
import com.android.server.display.config.RefreshRateRange;
import com.android.server.display.config.RefreshRateThrottlingMap;
@@ -219,6 +223,22 @@ import javax.xml.datatype.DatatypeConfigurationException;
* <allowInLowPowerMode>false</allowInLowPowerMode>
* </highBrightnessMode>
*
+ * <luxThrottling>
+ * <brightnessLimitMap>
+ * <type>default</type>
+ * <map>
+ * <point>
+ * <first>5000</first>
+ * <second>0.3</second>
+ * </point>
+ * <point>
+ * <first>5000</first>
+ * <second>0.3</second>
+ * </point>
+ * </map>
+ * </brightnessPeakMap>
+ * </luxThrottling>
+ *
* <quirks>
* <quirk>canSetBrightnessViaHwc</quirk>
* </quirks>
@@ -693,6 +713,9 @@ public class DisplayDeviceConfig {
private final Map<String, SparseArray<SurfaceControl.RefreshRateRange>>
mRefreshRateThrottlingMap = new HashMap<>();
+ private final Map<BrightnessLimitMapType, Map<Float, Float>>
+ mLuxThrottlingData = new HashMap<>();
+
@Nullable
private HostUsiVersion mHostUsiVersion;
@@ -1344,6 +1367,11 @@ public class DisplayDeviceConfig {
return hbmData;
}
+ @NonNull
+ public Map<BrightnessLimitMapType, Map<Float, Float>> getLuxThrottlingData() {
+ return mLuxThrottlingData;
+ }
+
public List<RefreshRateLimitation> getRefreshRateLimitations() {
return mRefreshRateLimitations;
}
@@ -1530,6 +1558,7 @@ public class DisplayDeviceConfig {
+ ", mBrightnessDefault=" + mBrightnessDefault
+ ", mQuirks=" + mQuirks
+ ", isHbmEnabled=" + mIsHighBrightnessModeEnabled
+ + ", mLuxThrottlingData=" + mLuxThrottlingData
+ ", mHbmData=" + mHbmData
+ ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline
+ ", mThermalBrightnessThrottlingDataMapByThrottlingId="
@@ -1676,6 +1705,7 @@ public class DisplayDeviceConfig {
loadBrightnessMap(config);
loadThermalThrottlingConfig(config);
loadHighBrightnessModeData(config);
+ loadLuxThrottling(config);
loadQuirks(config);
loadBrightnessRamps(config);
loadAmbientLightSensorFromDdc(config);
@@ -2026,7 +2056,7 @@ public class DisplayDeviceConfig {
/** Loads the refresh rate profiles. */
private void loadRefreshRateZoneProfiles(RefreshRateConfigs refreshRateConfigs) {
- if (refreshRateConfigs == null) {
+ if (refreshRateConfigs == null || refreshRateConfigs.getRefreshRateZoneProfiles() == null) {
return;
}
for (RefreshRateZone zone :
@@ -2428,6 +2458,54 @@ public class DisplayDeviceConfig {
}
}
+ private void loadLuxThrottling(DisplayConfiguration config) {
+ LuxThrottling cfg = config.getLuxThrottling();
+ if (cfg != null) {
+ HighBrightnessMode hbm = config.getHighBrightnessMode();
+ float hbmTransitionPoint = hbm != null ? hbm.getTransitionPoint_all().floatValue()
+ : PowerManager.BRIGHTNESS_MAX;
+ List<BrightnessLimitMap> limitMaps = cfg.getBrightnessLimitMap();
+ for (BrightnessLimitMap map : limitMaps) {
+ PredefinedBrightnessLimitNames type = map.getType();
+ BrightnessLimitMapType mappedType = BrightnessLimitMapType.convert(type);
+ if (mappedType == null) {
+ Slog.wtf(TAG, "Invalid NBM config: unsupported map type=" + type);
+ continue;
+ }
+ if (mLuxThrottlingData.containsKey(mappedType)) {
+ Slog.wtf(TAG, "Invalid NBM config: duplicate map type=" + mappedType);
+ continue;
+ }
+ Map<Float, Float> luxToTransitionPointMap = new HashMap<>();
+
+ List<NonNegativeFloatToFloatPoint> points = map.getMap().getPoint();
+ for (NonNegativeFloatToFloatPoint point : points) {
+ float lux = point.getFirst().floatValue();
+ float maxBrightness = point.getSecond().floatValue();
+ if (maxBrightness > hbmTransitionPoint) {
+ Slog.wtf(TAG,
+ "Invalid NBM config: maxBrightness is greater than hbm"
+ + ".transitionPoint. type="
+ + type + "; lux=" + lux + "; maxBrightness="
+ + maxBrightness);
+ continue;
+ }
+ if (luxToTransitionPointMap.containsKey(lux)) {
+ Slog.wtf(TAG,
+ "Invalid NBM config: duplicate lux key. type=" + type + "; lux="
+ + lux);
+ continue;
+ }
+ luxToTransitionPointMap.put(lux,
+ mBacklightToBrightnessSpline.interpolate(maxBrightness));
+ }
+ if (!luxToTransitionPointMap.isEmpty()) {
+ mLuxThrottlingData.put(mappedType, luxToTransitionPointMap);
+ }
+ }
+ }
+ }
+
private void loadBrightnessRamps(DisplayConfiguration config) {
// Priority 1: Value in the display device config (float)
// Priority 2: Value in the config.xml (int)
@@ -3155,4 +3233,19 @@ public class DisplayDeviceConfig {
}
}
}
+
+ public enum BrightnessLimitMapType {
+ DEFAULT, ADAPTIVE;
+
+ @Nullable
+ private static BrightnessLimitMapType convert(PredefinedBrightnessLimitNames type) {
+ switch (type) {
+ case _default:
+ return DEFAULT;
+ case adaptive:
+ return ADAPTIVE;
+ }
+ return null;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 9d31572c7d76..92f6d45fae66 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -445,7 +445,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
private final ColorDisplayServiceInternal mCdsi;
private float[] mNitsRange;
- private final HighBrightnessModeController mHbmController;
+ private final BrightnessRangeController mBrightnessRangeController;
private final HighBrightnessModeMetadata mHighBrightnessModeMetadata;
private final BrightnessThrottler mBrightnessThrottler;
@@ -654,8 +654,19 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
loadBrightnessRampRates();
mSkipScreenOnBrightnessRamp = resources.getBoolean(
com.android.internal.R.bool.config_skipScreenOnBrightnessRamp);
+ Runnable modeChangeCallback = () -> {
+ sendUpdatePowerState();
+ postBrightnessChangeRunnable();
+ // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
+ if (mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.update();
+ }
+ };
- mHbmController = createHbmControllerLocked();
+ HighBrightnessModeController hbmController = createHbmControllerLocked(modeChangeCallback);
+
+ mBrightnessRangeController = new BrightnessRangeController(hbmController,
+ modeChangeCallback, mDisplayDeviceConfig);
mBrightnessThrottler = createBrightnessThrottlerLocked();
@@ -802,7 +813,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
@Override
public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux) {
- mHbmController.onAmbientLuxChange(ambientLux);
+ mBrightnessRangeController.onAmbientLuxChange(ambientLux);
if (nits < 0) {
mBrightnessToFollow = leadDisplayBrightness;
} else {
@@ -1039,17 +1050,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mBrightnessRampIncreaseMaxTimeMillis,
mBrightnessRampDecreaseMaxTimeMillis);
}
- mHbmController.setHighBrightnessModeMetadata(hbmMetadata);
- mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
- mDisplayDeviceConfig.getHighBrightnessModeData(),
- new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
- @Override
- public float getHdrBrightnessFromSdr(
- float sdrBrightness, float maxDesiredHdrSdrRatio) {
- return mDisplayDeviceConfig.getHdrBrightnessFromSdr(
- sdrBrightness, maxDesiredHdrSdrRatio);
- }
- });
+ mBrightnessRangeController.loadFromConfig(hbmMetadata, token, info, mDisplayDeviceConfig);
mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(),
mThermalBrightnessThrottlingDataId, mUniqueDisplayId);
@@ -1264,7 +1265,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp,
ambientBrightnessThresholds, screenBrightnessThresholds,
ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext,
- mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper,
+ mBrightnessRangeController, mBrightnessThrottler, mIdleModeBrightnessMapper,
mDisplayDeviceConfig.getAmbientHorizonShort(),
mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userBrightness);
@@ -1364,7 +1365,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
/** Clean up all resources that are accessed via the {@link #mHandler} thread. */
private void cleanupHandlerThreadAfterStop() {
setProximitySensorEnabled(false);
- mHbmController.stop();
+ mBrightnessRangeController.stop();
mBrightnessThrottler.stop();
mHandler.removeCallbacksAndMessages(null);
@@ -1647,7 +1648,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mShouldResetShortTermModel);
mShouldResetShortTermModel = false;
}
- mHbmController.setAutoBrightnessEnabled(mUseAutoBrightness
+ mBrightnessRangeController.setAutoBrightnessEnabled(mUseAutoBrightness
? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
: AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
@@ -1820,7 +1821,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// here instead of having HbmController listen to the brightness setting because certain
// brightness sources (such as an app override) are not saved to the setting, but should be
// reflected in HBM calculations.
- mHbmController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
+ mBrightnessRangeController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
mBrightnessThrottler.getBrightnessMaxReason());
// Animate the screen brightness when the screen is on or dozing.
@@ -1874,13 +1875,14 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
float sdrAnimateValue = animateValue;
// TODO(b/216365040): The decision to prevent HBM for HDR in low power mode should be
// done in HighBrightnessModeController.
- if (mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
+ if (mBrightnessRangeController.getHighBrightnessMode()
+ == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
&& (mBrightnessReasonTemp.getModifier() & BrightnessReason.MODIFIER_DIMMED) == 0
&& (mBrightnessReasonTemp.getModifier() & BrightnessReason.MODIFIER_LOW_POWER)
== 0) {
// We want to scale HDR brightness level with the SDR level, we also need to restore
// SDR brightness immediately when entering dim or low power mode.
- animateValue = mHbmController.getHdrBrightnessValue();
+ animateValue = mBrightnessRangeController.getHdrBrightnessValue();
}
final float currentBrightness = mPowerState.getScreenBrightness();
@@ -1942,8 +1944,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mTempBrightnessEvent.setBrightness(brightnessState);
mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId);
mTempBrightnessEvent.setReason(mBrightnessReason);
- mTempBrightnessEvent.setHbmMax(mHbmController.getCurrentBrightnessMax());
- mTempBrightnessEvent.setHbmMode(mHbmController.getHighBrightnessMode());
+ mTempBrightnessEvent.setHbmMax(mBrightnessRangeController.getCurrentBrightnessMax());
+ mTempBrightnessEvent.setHbmMode(mBrightnessRangeController.getHighBrightnessMode());
mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
| (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0)
| (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0));
@@ -2104,9 +2106,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) {
synchronized (mCachedBrightnessInfo) {
- final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(),
+ final float minBrightness = Math.min(
+ mBrightnessRangeController.getCurrentBrightnessMin(),
mBrightnessThrottler.getBrightnessCap());
- final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(),
+ final float maxBrightness = Math.min(
+ mBrightnessRangeController.getCurrentBrightnessMax(),
mBrightnessThrottler.getBrightnessCap());
boolean changed = false;
@@ -2124,10 +2128,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
maxBrightness);
changed |=
mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode,
- mHbmController.getHighBrightnessMode());
+ mBrightnessRangeController.getHighBrightnessMode());
changed |=
mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint,
- mHbmController.getTransitionPoint());
+ mBrightnessRangeController.getTransitionPoint());
changed |=
mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
mBrightnessThrottler.getBrightnessMaxReason());
@@ -2137,10 +2141,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
void postBrightnessChangeRunnable() {
- mHandler.post(mOnBrightnessChangeRunnable);
+ if (!mHandler.hasCallbacks(mOnBrightnessChangeRunnable)) {
+ mHandler.post(mOnBrightnessChangeRunnable);
+ }
}
- private HighBrightnessModeController createHbmControllerLocked() {
+ private HighBrightnessModeController createHbmControllerLocked(
+ Runnable modeChangeCallback) {
final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
final IBinder displayToken =
@@ -2159,15 +2166,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
return mDisplayDeviceConfig.getHdrBrightnessFromSdr(
sdrBrightness, maxDesiredHdrSdrRatio);
}
- },
- () -> {
- sendUpdatePowerState();
- postBrightnessChangeRunnable();
- // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
- if (mAutomaticBrightnessController != null) {
- mAutomaticBrightnessController.update();
- }
- }, mHighBrightnessModeMetadata, mContext);
+ }, modeChangeCallback, mHighBrightnessModeMetadata, mContext);
}
private BrightnessThrottler createBrightnessThrottlerLocked() {
@@ -2328,8 +2327,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
if (Float.isNaN(value)) {
value = PowerManager.BRIGHTNESS_MIN;
}
- return MathUtils.constrain(value,
- mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax());
+ return MathUtils.constrain(value, mBrightnessRangeController.getCurrentBrightnessMin(),
+ mBrightnessRangeController.getCurrentBrightnessMax());
}
// Checks whether the brightness is within the valid brightness range, not including off.
@@ -3003,8 +3002,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mScreenOffBrightnessSensorController.dump(pw);
}
- if (mHbmController != null) {
- mHbmController.dump(pw);
+ if (mBrightnessRangeController != null) {
+ mBrightnessRangeController.dump(pw);
}
if (mBrightnessThrottler != null) {
@@ -3471,7 +3470,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
HysteresisLevels screenBrightnessThresholds,
HysteresisLevels ambientBrightnessThresholdsIdle,
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
- HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
+ BrightnessRangeController brightnessRangeController,
+ BrightnessThrottler brightnessThrottler,
BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
int ambientLightHorizonLong, float userLux, float userBrightness) {
return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor,
@@ -3480,9 +3480,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
brighteningLightDebounceConfig, darkeningLightDebounceConfig,
resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds,
screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
- screenBrightnessThresholdsIdle, context, hbmController, brightnessThrottler,
- idleModeBrightnessMapper, ambientLightHorizonShort, ambientLightHorizonLong,
- userLux, userBrightness);
+ screenBrightnessThresholdsIdle, context, brightnessRangeController,
+ brightnessThrottler, idleModeBrightnessMapper, ambientLightHorizonShort,
+ ambientLightHorizonLong, userLux, userBrightness);
}
BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 41e4671df1a7..e26be6851099 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -376,8 +376,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
private final ColorDisplayServiceInternal mCdsi;
private float[] mNitsRange;
- private final HighBrightnessModeController mHbmController;
- private final HighBrightnessModeMetadata mHighBrightnessModeMetadata;
+ private final BrightnessRangeController mBrightnessRangeController;
private final BrightnessThrottler mBrightnessThrottler;
@@ -489,7 +488,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mDisplayPowerProximityStateController = mInjector.getDisplayPowerProximityStateController(
mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(),
() -> updatePowerState(), mDisplayId, mSensorManager);
- mHighBrightnessModeMetadata = hbmMetadata;
mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(context, mDisplayId);
mTag = "DisplayPowerController2[" + mDisplayId + "]";
@@ -532,9 +530,22 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mSkipScreenOnBrightnessRamp = resources.getBoolean(
R.bool.config_skipScreenOnBrightnessRamp);
- mHbmController = createHbmControllerLocked();
+ Runnable modeChangeCallback = () -> {
+ sendUpdatePowerState();
+ postBrightnessChangeRunnable();
+ // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
+ if (mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.update();
+ }
+ };
+ HighBrightnessModeController hbmController = createHbmControllerLocked(hbmMetadata,
+ modeChangeCallback);
mBrightnessThrottler = createBrightnessThrottlerLocked();
+
+ mBrightnessRangeController = new BrightnessRangeController(hbmController,
+ modeChangeCallback, mDisplayDeviceConfig);
+
mDisplayBrightnessController =
new DisplayBrightnessController(context, null,
mDisplayId, mLogicalDisplay.getDisplayInfoLocked().brightnessDefault,
@@ -848,17 +859,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mBrightnessRampIncreaseMaxTimeMillis,
mBrightnessRampDecreaseMaxTimeMillis);
}
- mHbmController.setHighBrightnessModeMetadata(hbmMetadata);
- mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
- mDisplayDeviceConfig.getHighBrightnessModeData(),
- new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
- @Override
- public float getHdrBrightnessFromSdr(
- float sdrBrightness, float maxDesiredHdrSdrRatio) {
- return mDisplayDeviceConfig.getHdrBrightnessFromSdr(
- sdrBrightness, maxDesiredHdrSdrRatio);
- }
- });
+
+ mBrightnessRangeController.loadFromConfig(hbmMetadata, token, info, mDisplayDeviceConfig);
mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(),
mThermalBrightnessThrottlingDataId, mUniqueDisplayId);
@@ -1076,7 +1078,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp,
ambientBrightnessThresholds, screenBrightnessThresholds,
ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext,
- mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper,
+ mBrightnessRangeController, mBrightnessThrottler, mIdleModeBrightnessMapper,
mDisplayDeviceConfig.getAmbientHorizonShort(),
mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userBrightness);
mDisplayBrightnessController.setAutomaticBrightnessController(
@@ -1180,7 +1182,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
/** Clean up all resources that are accessed via the {@link #mHandler} thread. */
private void cleanupHandlerThreadAfterStop() {
mDisplayPowerProximityStateController.cleanup();
- mHbmController.stop();
+ mBrightnessRangeController.stop();
mBrightnessThrottler.stop();
mHandler.removeCallbacksAndMessages(null);
@@ -1295,7 +1297,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
&& (mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged()
|| userSetBrightnessChanged);
- mHbmController.setAutoBrightnessEnabled(mAutomaticBrightnessStrategy
+ mBrightnessRangeController.setAutoBrightnessEnabled(mAutomaticBrightnessStrategy
.shouldUseAutoBrightness()
? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
: AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
@@ -1452,7 +1454,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
// here instead of having HbmController listen to the brightness setting because certain
// brightness sources (such as an app override) are not saved to the setting, but should be
// reflected in HBM calculations.
- mHbmController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
+ mBrightnessRangeController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
mBrightnessThrottler.getBrightnessMaxReason());
// Animate the screen brightness when the screen is on or dozing.
@@ -1509,13 +1511,14 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
float sdrAnimateValue = animateValue;
// TODO(b/216365040): The decision to prevent HBM for HDR in low power mode should be
// done in HighBrightnessModeController.
- if (mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
+ if (mBrightnessRangeController.getHighBrightnessMode()
+ == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
&& (mBrightnessReasonTemp.getModifier() & BrightnessReason.MODIFIER_DIMMED) == 0
&& (mBrightnessReasonTemp.getModifier() & BrightnessReason.MODIFIER_LOW_POWER)
== 0) {
// We want to scale HDR brightness level with the SDR level, we also need to restore
// SDR brightness immediately when entering dim or low power mode.
- animateValue = mHbmController.getHdrBrightnessValue();
+ animateValue = mBrightnessRangeController.getHdrBrightnessValue();
}
final float currentBrightness = mPowerState.getScreenBrightness();
@@ -1579,8 +1582,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mTempBrightnessEvent.setBrightness(brightnessState);
mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId);
mTempBrightnessEvent.setReason(mBrightnessReason);
- mTempBrightnessEvent.setHbmMax(mHbmController.getCurrentBrightnessMax());
- mTempBrightnessEvent.setHbmMode(mHbmController.getHighBrightnessMode());
+ mTempBrightnessEvent.setHbmMax(mBrightnessRangeController.getCurrentBrightnessMax());
+ mTempBrightnessEvent.setHbmMode(mBrightnessRangeController.getHighBrightnessMode());
mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
| (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0)
| (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0));
@@ -1750,9 +1753,11 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) {
synchronized (mCachedBrightnessInfo) {
- final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(),
+ final float minBrightness = Math.min(
+ mBrightnessRangeController.getCurrentBrightnessMin(),
mBrightnessThrottler.getBrightnessCap());
- final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(),
+ final float maxBrightness = Math.min(
+ mBrightnessRangeController.getCurrentBrightnessMax(),
mBrightnessThrottler.getBrightnessCap());
boolean changed = false;
@@ -1770,10 +1775,10 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
maxBrightness);
changed |=
mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode,
- mHbmController.getHighBrightnessMode());
+ mBrightnessRangeController.getHighBrightnessMode());
changed |=
mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint,
- mHbmController.getTransitionPoint());
+ mBrightnessRangeController.getTransitionPoint());
changed |=
mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
mBrightnessThrottler.getBrightnessMaxReason());
@@ -1783,10 +1788,13 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
}
void postBrightnessChangeRunnable() {
- mHandler.post(mOnBrightnessChangeRunnable);
+ if (!mHandler.hasCallbacks(mOnBrightnessChangeRunnable)) {
+ mHandler.post(mOnBrightnessChangeRunnable);
+ }
}
- private HighBrightnessModeController createHbmControllerLocked() {
+ private HighBrightnessModeController createHbmControllerLocked(
+ HighBrightnessModeMetadata hbmMetadata, Runnable modeChangeCallback) {
final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
final IBinder displayToken =
@@ -1798,22 +1806,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
return new HighBrightnessModeController(mHandler, info.width, info.height, displayToken,
displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData,
- new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
- @Override
- public float getHdrBrightnessFromSdr(
- float sdrBrightness, float maxDesiredHdrSdrRatio) {
- return mDisplayDeviceConfig.getHdrBrightnessFromSdr(
- sdrBrightness, maxDesiredHdrSdrRatio);
- }
- },
- () -> {
- sendUpdatePowerState();
- postBrightnessChangeRunnable();
- // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
- if (mAutomaticBrightnessController != null) {
- mAutomaticBrightnessController.update();
- }
- }, mHighBrightnessModeMetadata, mContext);
+ (sdrBrightness, maxDesiredHdrSdrRatio) ->
+ mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness,
+ maxDesiredHdrSdrRatio), modeChangeCallback, hbmMetadata, mContext);
}
private BrightnessThrottler createBrightnessThrottlerLocked() {
@@ -1960,8 +1955,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
if (Float.isNaN(value)) {
value = PowerManager.BRIGHTNESS_MIN;
}
- return MathUtils.constrain(value,
- mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax());
+ return MathUtils.constrain(value, mBrightnessRangeController.getCurrentBrightnessMin(),
+ mBrightnessRangeController.getCurrentBrightnessMax());
}
private void animateScreenBrightness(float target, float sdrTarget, float rate) {
@@ -2195,7 +2190,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
@Override
public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux) {
- mHbmController.onAmbientLuxChange(ambientLux);
+ mBrightnessRangeController.onAmbientLuxChange(ambientLux);
if (nits < 0) {
mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness);
} else {
@@ -2374,8 +2369,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
dumpRbcEvents(pw);
- if (mHbmController != null) {
- mHbmController.dump(pw);
+ if (mBrightnessRangeController != null) {
+ mBrightnessRangeController.dump(pw);
}
if (mBrightnessThrottler != null) {
@@ -2840,7 +2835,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
HysteresisLevels screenBrightnessThresholds,
HysteresisLevels ambientBrightnessThresholdsIdle,
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
- HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
+ BrightnessRangeController brightnessModeController,
+ BrightnessThrottler brightnessThrottler,
BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
int ambientLightHorizonLong, float userLux, float userBrightness) {
return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor,
@@ -2849,9 +2845,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
brighteningLightDebounceConfig, darkeningLightDebounceConfig,
resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds,
screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
- screenBrightnessThresholdsIdle, context, hbmController, brightnessThrottler,
- idleModeBrightnessMapper, ambientLightHorizonShort, ambientLightHorizonLong,
- userLux, userBrightness);
+ screenBrightnessThresholdsIdle, context, brightnessModeController,
+ brightnessThrottler, idleModeBrightnessMapper, ambientLightHorizonShort,
+ ambientLightHorizonLong, userLux, userBrightness);
}
BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index c7c0fab6140d..7701bc6271ae 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -701,11 +701,15 @@ final class LocalDisplayAdapter extends DisplayAdapter {
maxDisplayMode == null ? mInfo.width : maxDisplayMode.getPhysicalWidth();
final int maxHeight =
maxDisplayMode == null ? mInfo.height : maxDisplayMode.getPhysicalHeight();
- mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
- mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
- mInfo.roundedCorners = RoundedCorners.fromResources(
- res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+ // We cannot determine cutouts and rounded corners of external displays.
+ if (mStaticDisplayInfo.isInternal) {
+ mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
+ mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+ mInfo.roundedCorners = RoundedCorners.fromResources(
+ res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+ }
+
mInfo.installOrientation = mStaticDisplayInfo.installOrientation;
mInfo.displayShape = DisplayShape.fromResources(
diff --git a/services/core/java/com/android/server/display/NormalBrightnessModeController.java b/services/core/java/com/android/server/display/NormalBrightnessModeController.java
new file mode 100644
index 000000000000..bd5ed23d4782
--- /dev/null
+++ b/services/core/java/com/android/server/display/NormalBrightnessModeController.java
@@ -0,0 +1,103 @@
+/*
+ * 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.server.display;
+
+import android.annotation.NonNull;
+import android.os.PowerManager;
+
+import com.android.server.display.DisplayDeviceConfig.BrightnessLimitMapType;
+
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Limits brightness for normal-brightness mode, based on ambient lux
+ **/
+class NormalBrightnessModeController {
+ @NonNull
+ private Map<BrightnessLimitMapType, Map<Float, Float>> mMaxBrightnessLimits = new HashMap<>();
+ private float mAmbientLux = Float.MAX_VALUE;
+ private boolean mAutoBrightnessEnabled = false;
+
+ // brightness limit in normal brightness mode, based on ambient lux.
+ private float mMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+
+ boolean onAmbientLuxChange(float ambientLux) {
+ mAmbientLux = ambientLux;
+ return recalculateMaxBrightness();
+ }
+
+ void dump(PrintWriter pw) {
+ pw.println("NormalBrightnessModeController:");
+ pw.println(" mAutoBrightnessEnabled=" + mAutoBrightnessEnabled);
+ pw.println(" mAmbientLux=" + mAmbientLux);
+ pw.println(" mMaxBrightness=" + mMaxBrightness);
+ pw.println(" mMaxBrightnessLimits=" + mMaxBrightnessLimits);
+ }
+
+ boolean setAutoBrightnessState(int state) {
+ boolean isEnabled = state == AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+ if (isEnabled != mAutoBrightnessEnabled) {
+ mAutoBrightnessEnabled = isEnabled;
+ return recalculateMaxBrightness();
+ }
+ return false;
+ }
+
+ float getCurrentBrightnessMax() {
+ return mMaxBrightness;
+ }
+
+ boolean resetNbmData(
+ @NonNull Map<BrightnessLimitMapType, Map<Float, Float>> maxBrightnessLimits) {
+ mMaxBrightnessLimits = maxBrightnessLimits;
+ return recalculateMaxBrightness();
+ }
+
+ private boolean recalculateMaxBrightness() {
+ float foundAmbientBoundary = Float.MAX_VALUE;
+ float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+
+ Map<Float, Float> maxBrightnessPoints = null;
+
+ if (mAutoBrightnessEnabled) {
+ maxBrightnessPoints = mMaxBrightnessLimits.get(BrightnessLimitMapType.ADAPTIVE);
+ }
+
+ if (maxBrightnessPoints == null) {
+ maxBrightnessPoints = mMaxBrightnessLimits.get(BrightnessLimitMapType.DEFAULT);
+ }
+
+ if (maxBrightnessPoints != null) {
+ for (Map.Entry<Float, Float> brightnessPoint : maxBrightnessPoints.entrySet()) {
+ float ambientBoundary = brightnessPoint.getKey();
+ // find ambient lux upper boundary closest to current ambient lux
+ if (ambientBoundary > mAmbientLux && ambientBoundary < foundAmbientBoundary) {
+ foundMaxBrightness = brightnessPoint.getValue();
+ foundAmbientBoundary = ambientBoundary;
+ }
+ }
+ }
+
+ if (mMaxBrightness != foundMaxBrightness) {
+ mMaxBrightness = foundMaxBrightness;
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
index b4613a76c751..efb36227c7bd 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
@@ -171,7 +171,7 @@ public class BatterySaverPolicy extends ContentObserver implements
true, /* disableAod */
true, /* disableLaunchBoost */
true, /* disableOptionalSensors */
- true, /* disableVibration */
+ false, /* disableVibration */
false, /* enableAdjustBrightness */
false, /* enableDataSaver */
true, /* enableFirewall */
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 27329e20bc8d..6821c40ec5d3 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -16244,7 +16244,7 @@ public class BatteryStatsImpl extends BatteryStats {
}
NP = in.readInt();
- if (NP > 1000) {
+ if (NP > 10000) {
throw new ParcelFormatException("File corrupt: too many processes " + NP);
}
for (int ip = 0; ip < NP; ip++) {
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
index d8e6c262359d..d770792dffec 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
@@ -17,6 +17,7 @@
package com.android.server.powerstats;
import android.content.Context;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import com.android.internal.util.FileRotator;
@@ -27,6 +28,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
+import java.util.Date;
import java.util.concurrent.locks.ReentrantLock;
/**
@@ -266,4 +268,51 @@ public class PowerStatsDataStorage {
mLock.unlock();
}
}
+
+ /**
+ * Dump stats about stored data.
+ */
+ public void dump(IndentingPrintWriter ipw) {
+ mLock.lock();
+ try {
+ final int versionDot = mDataStorageFilename.lastIndexOf('.');
+ final String beforeVersionDot = mDataStorageFilename.substring(0, versionDot);
+ final File[] files = mDataStorageDir.listFiles();
+
+ int number = 0;
+ int dataSize = 0;
+ long earliestLogEpochTime = Long.MAX_VALUE;
+ for (int i = 0; i < files.length; i++) {
+ // Check that the stems before the version match.
+ final File file = files[i];
+ final String fileName = file.getName();
+ if (files[i].getName().startsWith(beforeVersionDot)) {
+ number++;
+ dataSize += file.length();
+ final int firstTimeChar = fileName.lastIndexOf('.') + 1;
+ final int endChar = fileName.lastIndexOf('-');
+ try {
+ final Long startTime =
+ Long.parseLong(fileName.substring(firstTimeChar, endChar));
+ if (startTime != null && startTime < earliestLogEpochTime) {
+ earliestLogEpochTime = startTime;
+ }
+ } catch (NumberFormatException nfe) {
+ Slog.e(TAG,
+ "Failed to extract start time from file : " + fileName, nfe);
+ }
+ }
+ }
+
+ if (earliestLogEpochTime != Long.MAX_VALUE) {
+ ipw.println("Earliest data time : " + new Date(earliestLogEpochTime));
+ } else {
+ ipw.println("Failed to parse earliest data time!!!");
+ }
+ ipw.println("# files : " + number);
+ ipw.println("Total data size (B) : " + dataSize);
+ } finally {
+ mLock.unlock();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
index 39ead13b03fe..e80a86d73f90 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
@@ -30,6 +30,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.util.AtomicFile;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
@@ -354,4 +355,23 @@ public final class PowerStatsLogger extends Handler {
updateCacheFile(residencyCacheFilename, powerEntityBytes);
}
}
+
+ /**
+ * Dump stats about stored data.
+ */
+ public void dump(IndentingPrintWriter ipw) {
+ ipw.println("PowerStats Meter Data:");
+ ipw.increaseIndent();
+ mPowerStatsMeterStorage.dump(ipw);
+ ipw.decreaseIndent();
+ ipw.println("PowerStats Model Data:");
+ ipw.increaseIndent();
+ mPowerStatsModelStorage.dump(ipw);
+ ipw.decreaseIndent();
+ ipw.println("PowerStats State Residency Data:");
+ ipw.increaseIndent();
+ mPowerStatsResidencyStorage.dump(ipw);
+ ipw.decreaseIndent();
+ }
+
}
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index 2638f34fe7df..ffc9a0178971 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -31,6 +31,7 @@ import android.os.HandlerThread;
import android.os.Looper;
import android.os.UserHandle;
import android.power.PowerStatsInternal;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -176,18 +177,31 @@ public class PowerStatsService extends SystemService {
} else if ("residency".equals(args[1])) {
mPowerStatsLogger.writeResidencyDataToFile(fd);
}
- } else if (args.length == 0) {
- pw.println("PowerStatsService dumpsys: available PowerEntities");
+ } else {
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+ ipw.println("PowerStatsService dumpsys: available PowerEntities");
PowerEntity[] powerEntity = getPowerStatsHal().getPowerEntityInfo();
- PowerEntityUtils.dumpsys(powerEntity, pw);
+ ipw.increaseIndent();
+ PowerEntityUtils.dumpsys(powerEntity, ipw);
+ ipw.decreaseIndent();
- pw.println("PowerStatsService dumpsys: available Channels");
+ ipw.println("PowerStatsService dumpsys: available Channels");
Channel[] channel = getPowerStatsHal().getEnergyMeterInfo();
- ChannelUtils.dumpsys(channel, pw);
+ ipw.increaseIndent();
+ ChannelUtils.dumpsys(channel, ipw);
+ ipw.decreaseIndent();
- pw.println("PowerStatsService dumpsys: available EnergyConsumers");
+ ipw.println("PowerStatsService dumpsys: available EnergyConsumers");
EnergyConsumer[] energyConsumer = getPowerStatsHal().getEnergyConsumerInfo();
- EnergyConsumerUtils.dumpsys(energyConsumer, pw);
+ ipw.increaseIndent();
+ EnergyConsumerUtils.dumpsys(energyConsumer, ipw);
+ ipw.decreaseIndent();
+
+ ipw.println("PowerStatsService dumpsys: PowerStatsLogger stats");
+ ipw.increaseIndent();
+ mPowerStatsLogger.dump(ipw);
+ ipw.decreaseIndent();
+
}
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 6b65922c198e..9321930c0272 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3025,9 +3025,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
// Set to activity manager directly to make sure the state can be seen by the subsequent
// update of scheduling group.
proc.setRunningAnimationUnsafe();
- mH.removeMessages(H.UPDATE_PROCESS_ANIMATING_STATE, proc);
- mH.sendMessageDelayed(mH.obtainMessage(H.UPDATE_PROCESS_ANIMATING_STATE, proc),
+ mH.sendMessage(mH.obtainMessage(H.ADD_WAKEFULNESS_ANIMATING_REASON, proc));
+ mH.removeMessages(H.REMOVE_WAKEFULNESS_ANIMATING_REASON, proc);
+ mH.sendMessageDelayed(mH.obtainMessage(H.REMOVE_WAKEFULNESS_ANIMATING_REASON, proc),
DOZE_ANIMATING_STATE_RETAIN_TIME_MS);
+ Trace.instant(TRACE_TAG_WINDOW_MANAGER, "requestWakefulnessAnimating");
}
@Override
@@ -5653,9 +5655,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
final class H extends Handler {
static final int REPORT_TIME_TRACKER_MSG = 1;
- static final int UPDATE_PROCESS_ANIMATING_STATE = 2;
static final int END_POWER_MODE_UNKNOWN_VISIBILITY_MSG = 3;
static final int RESUME_FG_APP_SWITCH_MSG = 4;
+ static final int ADD_WAKEFULNESS_ANIMATING_REASON = 5;
+ static final int REMOVE_WAKEFULNESS_ANIMATING_REASON = 6;
static final int FIRST_ACTIVITY_TASK_MSG = 100;
static final int FIRST_SUPERVISOR_TASK_MSG = 200;
@@ -5672,13 +5675,23 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
tracker.deliverResult(mContext);
}
break;
- case UPDATE_PROCESS_ANIMATING_STATE: {
+ case ADD_WAKEFULNESS_ANIMATING_REASON: {
final WindowProcessController proc = (WindowProcessController) msg.obj;
synchronized (mGlobalLock) {
- proc.updateRunningRemoteOrRecentsAnimation();
+ proc.addAnimatingReason(
+ WindowProcessController.ANIMATING_REASON_WAKEFULNESS_CHANGE);
}
}
break;
+ case REMOVE_WAKEFULNESS_ANIMATING_REASON: {
+ final WindowProcessController proc = (WindowProcessController) msg.obj;
+ synchronized (mGlobalLock) {
+ proc.removeAnimatingReason(
+ WindowProcessController.ANIMATING_REASON_WAKEFULNESS_CHANGE);
+ }
+ Trace.instant(TRACE_TAG_WINDOW_MANAGER, "finishWakefulnessAnimating");
+ }
+ break;
case END_POWER_MODE_UNKNOWN_VISIBILITY_MSG: {
synchronized (mGlobalLock) {
mRetainPowerModeAndTopProcessState = false;
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 778951a545fa..98ee98ba67f7 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -248,7 +248,10 @@ class BLASTSyncEngine {
Slog.e(TAG, "WM sent Transaction to organized, but never received" +
" commit callback. Application ANR likely to follow.");
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- onCommitted(merged);
+ synchronized (mWm.mGlobalLock) {
+ onCommitted(merged.mNativeObject != 0
+ ? merged : mWm.mTransactionFactory.get());
+ }
}
};
CommitCallback callback = new CommitCallback();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 7d0f58e7efef..9f020e8bbe56 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1154,7 +1154,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
final boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options,
- boolean deferPause) {
+ boolean skipPause) {
ActivityRecord next = topRunningActivity(true /* focusableOnly */);
if (next == null || !next.canResumeByCompat()) {
return false;
@@ -1162,11 +1162,9 @@ class TaskFragment extends WindowContainer<WindowContainer> {
next.delayedResume = false;
- // If we are currently pausing an activity, then don't do anything until that is done.
- final boolean allPausedComplete = mRootWindowContainer.allPausedActivitiesComplete();
- if (!allPausedComplete) {
- ProtoLog.v(WM_DEBUG_STATES,
- "resumeTopActivity: Skip resume: some activity pausing.");
+ if (!skipPause && !mRootWindowContainer.allPausedActivitiesComplete()) {
+ // If we aren't skipping pause, then we have to wait for currently pausing activities.
+ ProtoLog.v(WM_DEBUG_STATES, "resumeTopActivity: Skip resume: some activity pausing.");
return false;
}
@@ -1230,7 +1228,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
lastResumed = lastFocusedRootTask.getTopResumedActivity();
}
- boolean pausing = !deferPause && taskDisplayArea.pauseBackTasks(next);
+ boolean pausing = !skipPause && taskDisplayArea.pauseBackTasks(next);
if (mResumedActivity != null) {
ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Pausing %s", mResumedActivity);
pausing |= startPausing(mTaskSupervisor.mUserLeaving, false /* uiSleeping */,
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index d7d2b4e9dde2..caec45c555ab 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -48,6 +48,7 @@ import static com.android.server.wm.WindowManagerService.MY_PID;
import static java.util.Objects.requireNonNull;
import android.Manifest;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -75,7 +76,6 @@ import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
-import android.view.IRemoteAnimationRunner;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -87,6 +87,8 @@ import com.android.server.wm.ActivityTaskManagerService.HotPath;
import java.io.IOException;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
@@ -249,11 +251,30 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
@Nullable
private ArrayMap<ActivityRecord, int[]> mRemoteActivities;
- /** Whether our process is currently running a {@link RecentsAnimation} */
- private boolean mRunningRecentsAnimation;
+ /**
+ * It can be set for a running transition player ({@link android.window.ITransitionPlayer}) or
+ * remote animators (running {@link android.window.IRemoteTransition}).
+ */
+ static final int ANIMATING_REASON_REMOTE_ANIMATION = 1;
+ /** It is set for wakefulness transition. */
+ static final int ANIMATING_REASON_WAKEFULNESS_CHANGE = 1 << 1;
+ /** Whether the legacy {@link RecentsAnimation} is running. */
+ static final int ANIMATING_REASON_LEGACY_RECENT_ANIMATION = 1 << 2;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ ANIMATING_REASON_REMOTE_ANIMATION,
+ ANIMATING_REASON_WAKEFULNESS_CHANGE,
+ ANIMATING_REASON_LEGACY_RECENT_ANIMATION,
+ })
+ @interface AnimatingReason {}
- /** Whether our process is currently running a {@link IRemoteAnimationRunner} */
- private boolean mRunningRemoteAnimation;
+ /**
+ * Non-zero if this process is currently running an important animation. This should be never
+ * set for system server.
+ */
+ @AnimatingReason
+ private int mAnimatingReasons;
// The bits used for mActivityStateFlags.
private static final int ACTIVITY_STATE_FLAG_IS_VISIBLE = 1 << 16;
@@ -1847,30 +1868,45 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
void setRunningRecentsAnimation(boolean running) {
- if (mRunningRecentsAnimation == running) {
- return;
+ if (running) {
+ addAnimatingReason(ANIMATING_REASON_LEGACY_RECENT_ANIMATION);
+ } else {
+ removeAnimatingReason(ANIMATING_REASON_LEGACY_RECENT_ANIMATION);
}
- mRunningRecentsAnimation = running;
- updateRunningRemoteOrRecentsAnimation();
}
void setRunningRemoteAnimation(boolean running) {
- if (mRunningRemoteAnimation == running) {
- return;
+ if (running) {
+ addAnimatingReason(ANIMATING_REASON_REMOTE_ANIMATION);
+ } else {
+ removeAnimatingReason(ANIMATING_REASON_REMOTE_ANIMATION);
+ }
+ }
+
+ void addAnimatingReason(@AnimatingReason int reason) {
+ final int prevReasons = mAnimatingReasons;
+ mAnimatingReasons |= reason;
+ if (prevReasons == 0) {
+ setAnimating(true);
}
- mRunningRemoteAnimation = running;
- updateRunningRemoteOrRecentsAnimation();
}
- void updateRunningRemoteOrRecentsAnimation() {
+ void removeAnimatingReason(@AnimatingReason int reason) {
+ final int prevReasons = mAnimatingReasons;
+ mAnimatingReasons &= ~reason;
+ if (prevReasons != 0 && mAnimatingReasons == 0) {
+ setAnimating(false);
+ }
+ }
+
+ /** Applies the animating state to activity manager for updating process priority. */
+ private void setAnimating(boolean animating) {
// Posting on handler so WM lock isn't held when we call into AM.
- mAtm.mH.sendMessage(PooledLambda.obtainMessage(
- WindowProcessListener::setRunningRemoteAnimation, mListener,
- isRunningRemoteTransition()));
+ mAtm.mH.post(() -> mListener.setRunningRemoteAnimation(animating));
}
boolean isRunningRemoteTransition() {
- return mRunningRecentsAnimation || mRunningRemoteAnimation;
+ return (mAnimatingReasons & ANIMATING_REASON_REMOTE_ANIMATION) != 0;
}
/** Adjusts scheduling group for animation. This method MUST NOT be called inside WM lock. */
@@ -1924,6 +1960,21 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
pw.println(prefix + " mLastReportedConfiguration=" + (mHasCachedConfiguration
? ("(cached) " + mLastReportedConfiguration) : mLastReportedConfiguration));
+ final int animatingReasons = mAnimatingReasons;
+ if (animatingReasons != 0) {
+ pw.print(prefix + " mAnimatingReasons=");
+ if ((animatingReasons & ANIMATING_REASON_REMOTE_ANIMATION) != 0) {
+ pw.print("remote-animation|");
+ }
+ if ((animatingReasons & ANIMATING_REASON_WAKEFULNESS_CHANGE) != 0) {
+ pw.print("wakefulness|");
+ }
+ if ((animatingReasons & ANIMATING_REASON_LEGACY_RECENT_ANIMATION) != 0) {
+ pw.print("legacy-recents");
+ }
+ pw.println();
+ }
+
final int stateFlags = mActivityStateFlags;
if (stateFlags != ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER) {
pw.print(prefix + " mActivityStateFlags=");
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index f96ca582c28f..7104a80c668d 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -46,6 +46,8 @@
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
+ <xs:element type="luxThrottling" name="luxThrottling" minOccurs="0"
+ maxOccurs="1"/>
<xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0"
maxOccurs="1"/>
<xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1"/>
@@ -137,6 +139,39 @@
</xs:sequence>
</xs:complexType>
+ <xs:complexType name="luxThrottling">
+ <xs:sequence>
+ <xs:element name="brightnessLimitMap" type="brightnessLimitMap"
+ maxOccurs="unbounded">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="brightnessLimitMap">
+ <xs:sequence>
+ <xs:element name="type" type="PredefinedBrightnessLimitNames">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- lux level from light sensor to screen brightness recommended max value map.
+ Screen brightness recommended max value is to highBrightnessMode.transitionPoint and must be below that -->
+ <xs:element name="map" type="nonNegativeFloatToFloatMap">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <!-- Predefined type names as defined by DisplayDeviceConfig.BrightnessLimitMapType -->
+ <xs:simpleType name="PredefinedBrightnessLimitNames">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="default"/>
+ <xs:enumeration value="adaptive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
<xs:complexType name="highBrightnessMode">
<xs:all>
<xs:element name="transitionPoint" type="nonNegativeDecimal" minOccurs="1"
@@ -575,4 +610,27 @@
<xs:annotation name="final"/>
</xs:element>
</xs:complexType>
+
+ <!-- generic types -->
+ <xs:complexType name="nonNegativeFloatToFloatPoint">
+ <xs:sequence>
+ <xs:element name="first" type="nonNegativeDecimal">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <xs:element name="second" type="nonNegativeDecimal">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="nonNegativeFloatToFloatMap">
+ <xs:sequence>
+ <xs:element name="point" type="nonNegativeFloatToFloatPoint" maxOccurs="unbounded">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
</xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index ad6434e0c545..507c9dccda59 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -26,6 +26,14 @@ package com.android.server.display.config {
method public final java.util.List<com.android.server.display.config.DisplayBrightnessPoint> getDisplayBrightnessPoint();
}
+ public class BrightnessLimitMap {
+ ctor public BrightnessLimitMap();
+ method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getMap();
+ method @NonNull public final com.android.server.display.config.PredefinedBrightnessLimitNames getType();
+ method public final void setMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap);
+ method public final void setType(@NonNull com.android.server.display.config.PredefinedBrightnessLimitNames);
+ }
+
public class BrightnessThresholds {
ctor public BrightnessThresholds();
method public final com.android.server.display.config.ThresholdPoints getBrightnessThresholdPoints();
@@ -89,6 +97,7 @@ package com.android.server.display.config {
method public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholdsIdle();
method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
method public final com.android.server.display.config.SensorDetails getLightSensor();
+ method public com.android.server.display.config.LuxThrottling getLuxThrottling();
method @Nullable public final String getName();
method public final com.android.server.display.config.SensorDetails getProxSensor();
method public com.android.server.display.config.DisplayQuirks getQuirks();
@@ -115,6 +124,7 @@ package com.android.server.display.config {
method public final void setDisplayBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds);
method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
method public final void setLightSensor(com.android.server.display.config.SensorDetails);
+ method public void setLuxThrottling(com.android.server.display.config.LuxThrottling);
method public final void setName(@Nullable String);
method public final void setProxSensor(com.android.server.display.config.SensorDetails);
method public void setQuirks(com.android.server.display.config.DisplayQuirks);
@@ -173,6 +183,11 @@ package com.android.server.display.config {
method public java.util.List<java.math.BigInteger> getItem();
}
+ public class LuxThrottling {
+ ctor public LuxThrottling();
+ method @NonNull public final java.util.List<com.android.server.display.config.BrightnessLimitMap> getBrightnessLimitMap();
+ }
+
public class NitsMap {
ctor public NitsMap();
method public String getInterpolation();
@@ -180,6 +195,19 @@ package com.android.server.display.config {
method public void setInterpolation(String);
}
+ public class NonNegativeFloatToFloatMap {
+ ctor public NonNegativeFloatToFloatMap();
+ method @NonNull public final java.util.List<com.android.server.display.config.NonNegativeFloatToFloatPoint> getPoint();
+ }
+
+ public class NonNegativeFloatToFloatPoint {
+ ctor public NonNegativeFloatToFloatPoint();
+ method @NonNull public final java.math.BigDecimal getFirst();
+ method @NonNull public final java.math.BigDecimal getSecond();
+ method public final void setFirst(@NonNull java.math.BigDecimal);
+ method public final void setSecond(@NonNull java.math.BigDecimal);
+ }
+
public class Point {
ctor public Point();
method @NonNull public final java.math.BigDecimal getNits();
@@ -188,6 +216,12 @@ package com.android.server.display.config {
method public final void setValue(@NonNull java.math.BigDecimal);
}
+ public enum PredefinedBrightnessLimitNames {
+ method public String getRawName();
+ enum_constant public static final com.android.server.display.config.PredefinedBrightnessLimitNames _default;
+ enum_constant public static final com.android.server.display.config.PredefinedBrightnessLimitNames adaptive;
+ }
+
public class RefreshRateConfigs {
ctor public RefreshRateConfigs();
method public final java.math.BigInteger getDefaultPeakRefreshRate();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 0c1d5c5e75e3..230ff5a40d54 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -679,7 +679,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// to decide whether an existing policy in the {@link #DEVICE_POLICIES_XML} needs to
// be upgraded. See {@link PolicyVersionUpgrader} on instructions how to add an upgrade
// step.
- static final int DPMS_VERSION = 5;
+ static final int DPMS_VERSION = 6;
static {
SECURE_SETTINGS_ALLOWLIST = new ArraySet<>();
@@ -875,8 +875,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private static final boolean DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG = true;
// TODO(b/265683382) remove the flag after rollout.
- private static final String KEEP_PROFILES_RUNNING_FLAG = "enable_keep_profiles_running";
- public static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = true;
+ public static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = false;
// TODO(b/261999445) remove the flag after rollout.
private static final String HEADLESS_FLAG = "headless";
@@ -23835,10 +23834,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
private static boolean isKeepProfilesRunningFlagEnabled() {
- return DeviceConfig.getBoolean(
- NAMESPACE_DEVICE_POLICY_MANAGER,
- KEEP_PROFILES_RUNNING_FLAG,
- DEFAULT_KEEP_PROFILES_RUNNING_FLAG);
+ return DEFAULT_KEEP_PROFILES_RUNNING_FLAG;
}
private boolean isUnicornFlagEnabled() {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java
index 733b1d98f3e2..f060426ec827 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java
@@ -117,6 +117,19 @@ public class PolicyVersionUpgrader {
currentVersion = 5;
}
+ if (currentVersion == 5) {
+ Slog.i(LOG_TAG, String.format("Upgrading from version %d", currentVersion));
+ // No-op upgrade here:
+ // DevicePolicyData.mEffectiveKeepProfilesRunning is only stored in XML file when it is
+ // different from its default value, otherwise the tag is not written. When loading, if
+ // the tag is missing, the field retains the value previously assigned in the
+ // constructor, which is the default value.
+ // In version 5 the default value was 'true', in version 6 it is 'false', so when
+ // loading XML version 5 we need to initialize the field to 'true' for it to be restored
+ // correctly in case the tag is missing. This is done in loadDataForUser().
+ currentVersion = 6;
+ }
+
writePoliciesAndVersion(allUsers, allUsersData, ownersData, currentVersion);
}
@@ -282,6 +295,10 @@ public class PolicyVersionUpgrader {
private DevicePolicyData loadDataForUser(
int userId, int loadVersion, ComponentName ownerComponent) {
DevicePolicyData policy = new DevicePolicyData(userId);
+ // See version 5 -> 6 step in upgradePolicy()
+ if (loadVersion == 5 && userId == UserHandle.USER_SYSTEM) {
+ policy.mEffectiveKeepProfilesRunning = true;
+ }
DevicePolicyData.load(policy,
mProvider.makeDevicePoliciesJournaledFile(userId),
mProvider.getAdminInfoSupplier(userId),
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index 32243f04f6e8..212a243c6a9e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -2221,7 +2221,7 @@ public class GameManagerServiceTests {
String[] packages = {mPackageName};
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
}
@@ -2238,12 +2238,12 @@ public class GameManagerServiceTests {
doAnswer(inv -> powerState.put(inv.getArgument(0), inv.getArgument(1)))
.when(mMockPowerManager).setPowerMode(anyInt(), anyBoolean());
gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
assertTrue(powerState.get(Mode.GAME));
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
- somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0);
assertTrue(powerState.get(Mode.GAME));
gameManagerService.mUidObserver.onUidStateChanged(
somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
@@ -2260,13 +2260,13 @@ public class GameManagerServiceTests {
int somePackageId = DEFAULT_PACKAGE_UID + 1;
when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
- somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
- somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
}
@@ -2277,9 +2277,9 @@ public class GameManagerServiceTests {
String[] packages = {mPackageName};
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 2def023de4f3..de5e6d7b3825 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -815,7 +815,7 @@ public final class DisplayPowerController2Test {
any(HysteresisLevels.class),
any(HysteresisLevels.class),
eq(mContext),
- any(HighBrightnessModeController.class),
+ any(BrightnessRangeController.class),
any(BrightnessThrottler.class),
isNull(),
anyInt(),
@@ -1064,7 +1064,7 @@ public final class DisplayPowerController2Test {
HysteresisLevels screenBrightnessThresholds,
HysteresisLevels ambientBrightnessThresholdsIdle,
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
- HighBrightnessModeController hbmController,
+ BrightnessRangeController brightnessRangeController,
BrightnessThrottler brightnessThrottler,
BrightnessMappingStrategy idleModeBrightnessMapper,
int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux,
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
index 6c80f7b5f4ab..95bc26ac99ed 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -819,7 +819,7 @@ public final class DisplayPowerControllerTest {
any(HysteresisLevels.class),
any(HysteresisLevels.class),
eq(mContext),
- any(HighBrightnessModeController.class),
+ any(BrightnessRangeController.class),
any(BrightnessThrottler.class),
isNull(),
anyInt(),
@@ -1038,7 +1038,7 @@ public final class DisplayPowerControllerTest {
HysteresisLevels screenBrightnessThresholds,
HysteresisLevels ambientBrightnessThresholdsIdle,
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
- HighBrightnessModeController hbmController,
+ BrightnessRangeController brightnessRangeController,
BrightnessThrottler brightnessThrottler,
BrightnessMappingStrategy idleModeBrightnessMapper,
int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux,
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index b7dbaf93b9e2..f89f73c98cfd 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -37,6 +37,7 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -1009,6 +1010,72 @@ public class LocalDisplayAdapterTest {
0.001f);
}
+ @Test
+ public void test_getDisplayDeviceInfoLocked_internalDisplay_usesCutoutAndCorners()
+ throws Exception {
+ setupCutoutAndRoundedCorners();
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ display.info.isInternal = true;
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ // Turn on / initialize
+ Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+ 0);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ mListener.changedDisplays.clear();
+
+ DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+
+ assertThat(info.displayCutout).isNotNull();
+ assertThat(info.displayCutout.getBoundingRectTop()).isEqualTo(new Rect(507, 33, 573, 99));
+ assertThat(info.roundedCorners).isNotNull();
+ assertThat(info.roundedCorners.getRoundedCorner(0).getRadius()).isEqualTo(5);
+ }
+
+ @Test public void test_getDisplayDeviceInfoLocked_externalDisplay_doesNotUseCutoutOrCorners()
+ throws Exception {
+ setupCutoutAndRoundedCorners();
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ display.info.isInternal = false;
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ // Turn on / initialize
+ Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+ 0);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ mListener.changedDisplays.clear();
+
+ DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+
+ assertThat(info.displayCutout).isNull();
+ assertThat(info.roundedCorners).isNull();
+ }
+
+ private void setupCutoutAndRoundedCorners() {
+ String sampleCutout = "M 507,66\n"
+ + "a 33,33 0 1 0 66,0 33,33 0 1 0 -66,0\n"
+ + "Z\n"
+ + "@left\n";
+ // Setup some default cutout
+ when(mMockedResources.getString(
+ com.android.internal.R.string.config_mainBuiltInDisplayCutout))
+ .thenReturn(sampleCutout);
+ when(mMockedResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.rounded_corner_radius)).thenReturn(5);
+ }
+
private void assertDisplayDpi(DisplayDeviceInfo info, int expectedPort,
float expectedXdpi,
float expectedYDpi,
diff --git a/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_false.xml b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_false.xml
new file mode 100644
index 000000000000..4785a881638f
--- /dev/null
+++ b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_false.xml
@@ -0,0 +1,10 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true" provisioning-state="3">
+ <keep-profiles-running value="false" />
+ <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
+ <policies flags="991" />
+ <strong-auth-unlock-timeout value="0" />
+ <organization-color value="-16738680" />
+ <active-password value="0" />
+ </admin>
+</policies>
diff --git a/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_true.xml b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_true.xml
new file mode 100644
index 000000000000..07ec229fa267
--- /dev/null
+++ b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_true.xml
@@ -0,0 +1,10 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true" provisioning-state="3">
+ <keep-profiles-running value="true" />
+ <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
+ <policies flags="991" />
+ <strong-auth-unlock-timeout value="0" />
+ <organization-color value="-16738680" />
+ <active-password value="0" />
+ </admin>
+</policies>
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 662477ddbbe9..769be177ce03 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -26,6 +26,7 @@ import static android.hardware.biometrics.BiometricPrompt.DISMISSED_REASON_NEGAT
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_CALLED;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED_UI_SHOWING;
+import static com.android.server.biometrics.BiometricServiceStateProto.STATE_ERROR_PENDING_SYSUI;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -48,6 +49,7 @@ import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustManager;
import android.content.Context;
import android.content.res.Resources;
+import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.ComponentInfoInternal;
@@ -104,6 +106,7 @@ public class AuthSessionTest {
@Mock private KeyStore mKeyStore;
@Mock private AuthSession.ClientDeathReceiver mClientDeathReceiver;
@Mock private BiometricFrameworkStatsLogger mBiometricFrameworkStatsLogger;
+ @Mock private BiometricCameraManager mBiometricCameraManager;
private Random mRandom;
private IBinder mToken;
@@ -210,6 +213,40 @@ public class AuthSessionTest {
}
@Test
+ public void testOnErrorReceived_lockoutError() throws RemoteException {
+ setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_REAR);
+ setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
+ mock(IBiometricAuthenticator.class));
+ final AuthSession session = createAuthSession(mSensors,
+ false /* checkDevicePolicyManager */,
+ Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
+ 0 /* operationId */,
+ 0 /* userId */);
+ session.goToInitialState();
+ for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
+ assertEquals(BiometricSensor.STATE_WAITING_FOR_COOKIE, sensor.getSensorState());
+ session.onCookieReceived(
+ session.mPreAuthInfo.eligibleSensors.get(sensor.id).getCookie());
+ }
+ assertTrue(session.allCookiesReceived());
+ assertEquals(STATE_AUTH_STARTED, session.getState());
+
+ // Either of strong sensor's lockout should cancel both sensors.
+ final int cookie1 = session.mPreAuthInfo.eligibleSensors.get(0).getCookie();
+ session.onErrorReceived(0, cookie1, BiometricConstants.BIOMETRIC_ERROR_LOCKOUT, 0);
+ for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
+ assertEquals(BiometricSensor.STATE_CANCELING, sensor.getSensorState());
+ }
+ assertEquals(STATE_ERROR_PENDING_SYSUI, session.getState());
+
+ // If the sensor is STATE_CANCELING, delayed onAuthenticationRejected() shouldn't change the
+ // session state to STATE_AUTH_PAUSED.
+ session.onAuthenticationRejected(1);
+ assertEquals(STATE_ERROR_PENDING_SYSUI, session.getState());
+ }
+
+ @Test
public void testCancelReducesAppetiteForCookies() throws Exception {
setupFace(0 /* id */, false /* confirmationAlwaysRequired */,
mock(IBiometricAuthenticator.class));
@@ -314,6 +351,8 @@ public class AuthSessionTest {
assertEquals(startFingerprintNow ? BiometricSensor.STATE_AUTHENTICATING
: BiometricSensor.STATE_COOKIE_RETURNED,
session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState());
+ verify(mBiometricContext).updateContext((OperationContextExt) anyObject(),
+ eq(session.isCrypto()));
// start fingerprint sensor if it was delayed
if (!startFingerprintNow) {
@@ -571,7 +610,8 @@ public class AuthSessionTest {
promptInfo,
TEST_PACKAGE,
checkDevicePolicyManager,
- mContext);
+ mContext,
+ mBiometricCameraManager);
}
private AuthSession createAuthSession(List<BiometricSensor> sensors,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 67be37616d5f..fc62e75b7d59 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -16,9 +16,9 @@
package com.android.server.biometrics;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
-import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI;
@@ -114,6 +114,10 @@ public class BiometricServiceTest {
private static final String ERROR_UNABLE_TO_PROCESS = "error_unable_to_process";
private static final String ERROR_USER_CANCELED = "error_user_canceled";
private static final String ERROR_LOCKOUT = "error_lockout";
+ private static final String FACE_SUBTITLE = "face_subtitle";
+ private static final String FINGERPRINT_SUBTITLE = "fingerprint_subtitle";
+ private static final String DEFAULT_SUBTITLE = "default_subtitle";
+
private static final String FINGERPRINT_ACQUIRED_SENSOR_DIRTY = "sensor_dirty";
@@ -152,6 +156,8 @@ public class BiometricServiceTest {
private AuthSessionCoordinator mAuthSessionCoordinator;
@Mock
private UserManager mUserManager;
+ @Mock
+ private BiometricCameraManager mBiometricCameraManager;
BiometricContextProvider mBiometricContextProvider;
@@ -178,13 +184,24 @@ public class BiometricServiceTest {
when(mInjector.getDevicePolicyManager(any())).thenReturn(mDevicePolicyManager);
when(mInjector.getRequestGenerator()).thenReturn(() -> TEST_REQUEST_ID);
when(mInjector.getUserManager(any())).thenReturn(mUserManager);
+ when(mInjector.getBiometricCameraManager(any())).thenReturn(mBiometricCameraManager);
when(mResources.getString(R.string.biometric_error_hw_unavailable))
.thenReturn(ERROR_HW_UNAVAILABLE);
when(mResources.getString(R.string.biometric_not_recognized))
.thenReturn(ERROR_NOT_RECOGNIZED);
+ when(mResources.getString(R.string.biometric_face_not_recognized))
+ .thenReturn(ERROR_NOT_RECOGNIZED);
+ when(mResources.getString(R.string.fingerprint_error_not_match))
+ .thenReturn(ERROR_NOT_RECOGNIZED);
when(mResources.getString(R.string.biometric_error_user_canceled))
.thenReturn(ERROR_USER_CANCELED);
+ when(mContext.getString(R.string.biometric_dialog_face_subtitle))
+ .thenReturn(FACE_SUBTITLE);
+ when(mContext.getString(R.string.biometric_dialog_fingerprint_subtitle))
+ .thenReturn(FINGERPRINT_SUBTITLE);
+ when(mContext.getString(R.string.biometric_dialog_default_subtitle))
+ .thenReturn(DEFAULT_SUBTITLE);
when(mWindowManager.getDefaultDisplay()).thenReturn(
new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
@@ -205,7 +222,7 @@ public class BiometricServiceTest {
@Test
public void testClientBinderDied_whenPaused() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
true /* requireConfirmation */, null /* authenticators */);
@@ -232,7 +249,7 @@ public class BiometricServiceTest {
@Test
public void testClientBinderDied_whenAuthenticating() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
true /* requireConfirmation */, null /* authenticators */);
@@ -368,7 +385,7 @@ public class BiometricServiceTest {
final int[] modalities = new int[] {
TYPE_FINGERPRINT,
- BiometricAuthenticator.TYPE_FACE,
+ TYPE_FACE,
};
final int[] strengths = new int[] {
@@ -421,9 +438,56 @@ public class BiometricServiceTest {
}
@Test
+ public void testAuthenticateFace_shouldShowSubtitleForFace() throws Exception {
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */,
+ null);
+ waitForIdle();
+
+ assertEquals(FACE_SUBTITLE, mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
+ }
+
+ @Test
+ public void testAuthenticateFingerprint_shouldShowSubtitleForFingerprint() throws Exception {
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */,
+ null);
+ waitForIdle();
+
+ assertEquals(FINGERPRINT_SUBTITLE,
+ mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
+ }
+
+ @Test
+ public void testAuthenticateBothFpAndFace_shouldShowDefaultSubtitle() throws Exception {
+ final int[] modalities = new int[] {
+ TYPE_FINGERPRINT,
+ TYPE_FACE,
+ };
+
+ final int[] strengths = new int[] {
+ Authenticators.BIOMETRIC_WEAK,
+ Authenticators.BIOMETRIC_STRONG,
+ };
+
+ setupAuthForMultiple(modalities, strengths);
+
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */,
+ null);
+ waitForIdle();
+
+ assertEquals(DEFAULT_SUBTITLE, mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
+ }
+
+ @Test
public void testAuthenticateFace_respectsUserSetting()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
// Disabled in user settings receives onError
when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
@@ -562,7 +626,7 @@ public class BiometricServiceTest {
@Test
public void testAuthenticate_noBiometrics_credentialAllowed() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
.thenReturn(true);
@@ -589,13 +653,13 @@ public class BiometricServiceTest {
@Test
public void testAuthenticate_happyPathWithConfirmation_strongBiometric() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
testAuthenticate_happyPathWithConfirmation(true /* isStrongBiometric */);
}
@Test
public void testAuthenticate_happyPathWithConfirmation_weakBiometric() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_WEAK);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_WEAK);
testAuthenticate_happyPathWithConfirmation(false /* isStrongBiometric */);
}
@@ -631,7 +695,7 @@ public class BiometricServiceTest {
@Test
public void testAuthenticate_no_Biometrics_noCredential() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
.thenReturn(false);
@@ -649,7 +713,7 @@ public class BiometricServiceTest {
@Test
public void testRejectFace_whenAuthenticating_notifiesSystemUIAndClient_thenPaused()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -657,7 +721,7 @@ public class BiometricServiceTest {
waitForIdle();
verify(mBiometricService.mStatusBarService).onBiometricError(
- eq(BiometricAuthenticator.TYPE_FACE),
+ eq(TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_PAUSED_REJECTED),
eq(0 /* vendorCode */));
verify(mReceiver1).onAuthenticationFailed();
@@ -685,7 +749,7 @@ public class BiometricServiceTest {
@Test
public void testRequestAuthentication_whenAlreadyAuthenticating() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -694,7 +758,7 @@ public class BiometricServiceTest {
waitForIdle();
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FACE),
+ eq(TYPE_FACE),
eq(BiometricPrompt.BIOMETRIC_ERROR_CANCELED),
eq(0) /* vendorCode */);
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(eq(TEST_REQUEST_ID));
@@ -704,7 +768,7 @@ public class BiometricServiceTest {
@Test
public void testErrorHalTimeout_whenAuthenticating_entersPausedState() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -717,7 +781,7 @@ public class BiometricServiceTest {
assertEquals(STATE_AUTH_PAUSED, mBiometricService.mAuthSession.getState());
verify(mBiometricService.mStatusBarService).onBiometricError(
- eq(BiometricAuthenticator.TYPE_FACE),
+ eq(TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_TIMEOUT),
eq(0 /* vendorCode */));
// Timeout does not count as fail as per BiometricPrompt documentation.
@@ -753,7 +817,7 @@ public class BiometricServiceTest {
@Test
public void testErrorFromHal_whenPaused_notifiesSystemUIAndClient() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -771,7 +835,7 @@ public class BiometricServiceTest {
// Client receives error immediately
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FACE),
+ eq(TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
eq(0 /* vendorCode */));
// Dialog is hidden immediately
@@ -903,6 +967,45 @@ public class BiometricServiceTest {
}
@Test
+ public void testMultiBiometricAuth_whenLockoutTimed_sendsErrorAndModality()
+ throws Exception {
+ testMultiBiometricAuth_whenLockout(LockoutTracker.LOCKOUT_TIMED,
+ BiometricPrompt.BIOMETRIC_ERROR_LOCKOUT);
+ }
+
+ @Test
+ public void testMultiBiometricAuth_whenLockoutPermanent_sendsErrorAndModality()
+ throws Exception {
+ testMultiBiometricAuth_whenLockout(LockoutTracker.LOCKOUT_PERMANENT,
+ BiometricPrompt.BIOMETRIC_ERROR_LOCKOUT_PERMANENT);
+ }
+
+ private void testMultiBiometricAuth_whenLockout(@LockoutTracker.LockoutMode int lockoutMode,
+ int biometricPromptError) throws Exception {
+ final int[] modalities = new int[] {
+ TYPE_FINGERPRINT,
+ TYPE_FACE,
+ };
+
+ final int[] strengths = new int[] {
+ Authenticators.BIOMETRIC_STRONG,
+ Authenticators.BIOMETRIC_STRONG,
+ };
+ setupAuthForMultiple(modalities, strengths);
+
+ when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
+ .thenReturn(lockoutMode);
+ when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */, null /* authenticators */);
+ waitForIdle();
+
+ // The lockout error should be sent, instead of ERROR_NONE_ENROLLED. See b/286923477.
+ verify(mReceiver1).onError(eq(TYPE_FINGERPRINT),
+ eq(biometricPromptError), eq(0) /* vendorCode */);
+ }
+
+ @Test
public void testBiometricOrCredentialAuth_whenBiometricLockout_showsCredential()
throws Exception {
when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true);
@@ -1078,7 +1181,7 @@ public class BiometricServiceTest {
@Test
public void testDismissedReasonNegative_whilePaused_invokeHalCancel() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -1097,7 +1200,7 @@ public class BiometricServiceTest {
@Test
public void testDismissedReasonUserCancel_whilePaused_invokesHalCancel() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -1116,7 +1219,7 @@ public class BiometricServiceTest {
@Test
public void testDismissedReasonUserCancel_whenPendingConfirmation() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
true /* requireConfirmation */, null /* authenticators */);
@@ -1130,7 +1233,7 @@ public class BiometricServiceTest {
verify(mBiometricService.mSensors.get(0).impl)
.cancelAuthenticationFromService(any(), any(), anyLong());
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FACE),
+ eq(TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
eq(0 /* vendorCode */));
verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
@@ -1251,7 +1354,7 @@ public class BiometricServiceTest {
@Test
public void testCanAuthenticate_whenBiometricsNotEnabledForApps() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
.thenReturn(true);
@@ -1545,7 +1648,7 @@ public class BiometricServiceTest {
@Test
public void testWorkAuthentication_faceWorksIfNotDisabledByDevicePolicyManager()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
when(mDevicePolicyManager
.getKeyguardDisabledFeatures(any() /* admin*/, anyInt() /* userHandle */))
.thenReturn(~DevicePolicyManager.KEYGUARD_DISABLE_FACE);
@@ -1638,7 +1741,7 @@ public class BiometricServiceTest {
mFingerprintAuthenticator);
}
- if ((modality & BiometricAuthenticator.TYPE_FACE) != 0) {
+ if ((modality & TYPE_FACE) != 0) {
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(enrolled);
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
when(mFaceAuthenticator.getLockoutModeForUser(anyInt()))
@@ -1670,7 +1773,7 @@ public class BiometricServiceTest {
strength, mFingerprintAuthenticator);
}
- if ((modality & BiometricAuthenticator.TYPE_FACE) != 0) {
+ if ((modality & TYPE_FACE) != 0) {
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FACE, modality,
@@ -1753,6 +1856,7 @@ public class BiometricServiceTest {
boolean checkDevicePolicy) {
final PromptInfo promptInfo = new PromptInfo();
promptInfo.setConfirmationRequested(requireConfirmation);
+ promptInfo.setUseDefaultSubtitle(true);
if (authenticators != null) {
promptInfo.setAuthenticators(authenticators);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
new file mode 100644
index 000000000000..c2bdf501198e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.server.biometrics;
+
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+
+import static com.android.server.biometrics.sensors.LockoutTracker.LOCKOUT_NONE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.trust.ITrustManager;
+import android.content.Context;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.IBiometricAuthenticator;
+import android.hardware.biometrics.PromptInfo;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class PreAuthInfoTest {
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ private static final int SENSOR_ID_FACE = 1;
+ private static final String TEST_PACKAGE_NAME = "PreAuthInfoTestPackage";
+
+ @Mock
+ IBiometricAuthenticator mFaceAuthenticator;
+ @Mock
+ Context mContext;
+ @Mock
+ ITrustManager mTrustManager;
+ @Mock
+ DevicePolicyManager mDevicePolicyManager;
+ @Mock
+ BiometricService.SettingObserver mSettingObserver;
+ @Mock
+ BiometricCameraManager mBiometricCameraManager;
+
+ @Before
+ public void setup() throws RemoteException {
+ when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true);
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt()))
+ .thenReturn(KEYGUARD_DISABLE_FEATURES_NONE);
+ when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+ when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+ when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
+ when(mFaceAuthenticator.getLockoutModeForUser(anyInt()))
+ .thenReturn(LOCKOUT_NONE);
+ when(mBiometricCameraManager.isCameraPrivacyEnabled()).thenReturn(false);
+ when(mBiometricCameraManager.isAnyCameraUnavailable()).thenReturn(false);
+ }
+
+ @Test
+ public void testFaceAuthentication_whenCameraPrivacyIsEnabled() throws Exception {
+ when(mBiometricCameraManager.isCameraPrivacyEnabled()).thenReturn(true);
+
+ BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
+ BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
+ @Override
+ boolean confirmationAlwaysRequired(int userId) {
+ return false;
+ }
+
+ @Override
+ boolean confirmationSupported() {
+ return false;
+ }
+ };
+ PromptInfo promptInfo = new PromptInfo();
+ promptInfo.setConfirmationRequested(false /* requireConfirmation */);
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
+ PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+ mSettingObserver, List.of(sensor),
+ 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+
+ assertThat(preAuthInfo.eligibleSensors).isEmpty();
+ }
+
+ @Test
+ public void testFaceAuthentication_whenCameraPrivacyIsDisabledAndCameraIsAvailable()
+ throws Exception {
+ BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
+ BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
+ @Override
+ boolean confirmationAlwaysRequired(int userId) {
+ return false;
+ }
+
+ @Override
+ boolean confirmationSupported() {
+ return false;
+ }
+ };
+ PromptInfo promptInfo = new PromptInfo();
+ promptInfo.setConfirmationRequested(false /* requireConfirmation */);
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
+ PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+ mSettingObserver, List.of(sensor),
+ 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+
+ assertThat(preAuthInfo.eligibleSensors).hasSize(1);
+ }
+
+ @Test
+ public void testFaceAuthentication_whenCameraIsUnavailable() throws RemoteException {
+ when(mBiometricCameraManager.isAnyCameraUnavailable()).thenReturn(true);
+ BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
+ BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
+ @Override
+ boolean confirmationAlwaysRequired(int userId) {
+ return false;
+ }
+
+ @Override
+ boolean confirmationSupported() {
+ return false;
+ }
+ };
+ PromptInfo promptInfo = new PromptInfo();
+ promptInfo.setConfirmationRequested(false /* requireConfirmation */);
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
+ PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+ mSettingObserver, List.of(sensor),
+ 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+
+ assertThat(preAuthInfo.eligibleSensors).hasSize(0);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index e9d82696affc..89299002a227 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -19,6 +19,8 @@ package com.android.server.biometrics.sensors;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_SUCCESS;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
@@ -405,6 +407,59 @@ public class BiometricSchedulerTest {
testCancelsEnrollWhenRequestId(10L, 20, false /* started */);
}
+ @Test
+ public void testCancelAuthenticationClientWithoutStarting() {
+ final Supplier<Object> lazyDaemon = () -> mock(Object.class);
+ final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, lazyDaemon);
+ final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+ final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
+ mToken, callback, mBiometricContext);
+
+ //Schedule authentication client to the pending queue
+ mScheduler.scheduleClientMonitor(client1);
+ mScheduler.scheduleClientMonitor(client2);
+ waitForIdle();
+
+ assertThat(mScheduler.getCurrentClient()).isEqualTo(client1);
+
+ client2.cancel();
+ waitForIdle();
+
+ assertThat(client2.isAlreadyCancelled()).isTrue();
+
+ client1.getCallback().onClientFinished(client1, false);
+ waitForIdle();
+
+ assertThat(mScheduler.getCurrentClient()).isNull();
+ }
+
+ @Test
+ public void testCancelAuthenticationClientWithoutStarting_whenAppCrashes() {
+ final Supplier<Object> lazyDaemon = () -> mock(Object.class);
+ final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, lazyDaemon);
+ final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+ final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
+ mToken, callback, mBiometricContext);
+
+ //Schedule authentication client to the pending queue
+ mScheduler.scheduleClientMonitor(client1);
+ mScheduler.scheduleClientMonitor(client2);
+ waitForIdle();
+
+ assertThat(mScheduler.getCurrentClient()).isEqualTo(client1);
+
+ //App crashes
+ client2.binderDied();
+ waitForIdle();
+
+ assertThat(client2.isAlreadyCancelled()).isTrue();
+
+ client1.getCallback().onClientFinished(client1, false);
+ waitForIdle();
+
+ assertThat(mScheduler.getCurrentClient()).isNull();
+ }
+
private void testCancelsEnrollWhenRequestId(@Nullable Long requestId, long cancelRequestId,
boolean started) {
final Supplier<Object> lazyDaemon = () -> mock(Object.class);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 3f20f8a0bcd9..359d711c0750 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -16,8 +16,10 @@
package com.android.server.biometrics.sensors.face.aidl;
+import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -183,7 +185,9 @@ public class FaceAuthenticationClientTest {
client.onAuthenticated(new Face("friendly", 1 /* faceId */, 2 /* deviceId */),
true /* authenticated */, new ArrayList<>());
- verify(mCancellationSignal).cancel();
+ verify(mCancellationSignal, never()).cancel();
+ verify(mClientMonitorCallbackConverter)
+ .onError(anyInt(), anyInt(), eq(BIOMETRIC_ERROR_CANCELED), anyInt());
}
private FaceAuthenticationClient createClient() throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index f8f40fe44457..c383a96d5de3 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
+import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -399,7 +401,9 @@ public class FingerprintAuthenticationClientTest {
mLooper.moveTimeForward(10);
mLooper.dispatchAll();
- verify(mCancellationSignal).cancel();
+ verify(mCancellationSignal, never()).cancel();
+ verify(mClientMonitorCallbackConverter)
+ .onError(anyInt(), anyInt(), eq(BIOMETRIC_ERROR_CANCELED), anyInt());
}
private FingerprintAuthenticationClient createClient() throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
index eb2ee35161ff..d2b921deb0d9 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
@@ -76,7 +76,7 @@ import javax.xml.parsers.DocumentBuilderFactory;
public class PolicyVersionUpgraderTest extends DpmTestBase {
// NOTE: Only change this value if the corresponding CL also adds a test to test the upgrade
// to the new version.
- private static final int LATEST_TESTED_VERSION = 5;
+ private static final int LATEST_TESTED_VERSION = 6;
public static final String PERMISSIONS_TAG = "admin-can-grant-sensors-permissions";
public static final String DEVICE_OWNER_XML = "device_owner_2.xml";
private ComponentName mFakeAdmin;
@@ -313,7 +313,7 @@ public class PolicyVersionUpgraderTest extends DpmTestBase {
}
@Test
- public void testEffectiveKeepProfilesRunningSet() throws Exception {
+ public void testEffectiveKeepProfilesRunningSetToFalse4To5() throws Exception {
writeVersionToXml(4);
final int userId = UserHandle.USER_SYSTEM;
@@ -327,10 +327,111 @@ public class PolicyVersionUpgraderTest extends DpmTestBase {
Document policies = readPolicies(userId);
Element keepProfilesRunning = (Element) policies.getDocumentElement()
.getElementsByTagName("keep-profiles-running").item(0);
- assertThat(keepProfilesRunning.getAttribute("value")).isEqualTo("false");
+
+ // Default value (false) is not serialized.
+ assertThat(keepProfilesRunning).isNull();
+ }
+ @Test
+ public void testEffectiveKeepProfilesRunningIsToFalse4To6() throws Exception {
+ writeVersionToXml(4);
+
+ final int userId = UserHandle.USER_SYSTEM;
+ mProvider.mUsers = new int[]{userId};
+ preparePoliciesFile(userId, "device_policies.xml");
+
+ mUpgrader.upgradePolicy(6);
+
+ assertThat(readVersionFromXml()).isAtLeast(6);
+
+ Document policies = readPolicies(userId);
+ Element keepProfilesRunning = (Element) policies.getDocumentElement()
+ .getElementsByTagName("keep-profiles-running").item(0);
+
+ // Default value (false) is not serialized.
+ assertThat(keepProfilesRunning).isNull();
+ }
+
+ /**
+ * Verify correct behaviour when upgrading from Android 13
+ */
+ @Test
+ public void testEffectiveKeepProfilesRunningIsToFalse3To6() throws Exception {
+ writeVersionToXml(3);
+
+ final int userId = UserHandle.USER_SYSTEM;
+ mProvider.mUsers = new int[]{userId};
+ preparePoliciesFile(userId, "device_policies.xml");
+
+ mUpgrader.upgradePolicy(6);
+
+ assertThat(readVersionFromXml()).isAtLeast(6);
+
+ Document policies = readPolicies(userId);
+ Element keepProfilesRunning = (Element) policies.getDocumentElement()
+ .getElementsByTagName("keep-profiles-running").item(0);
+
+ // Default value (false) is not serialized.
+ assertThat(keepProfilesRunning).isNull();
}
@Test
+ public void testEffectiveKeepProfilesRunningMissingInV5() throws Exception {
+ writeVersionToXml(5);
+
+ final int userId = UserHandle.USER_SYSTEM;
+ mProvider.mUsers = new int[]{userId};
+ preparePoliciesFile(userId, "device_policies.xml");
+
+ mUpgrader.upgradePolicy(6);
+
+ assertThat(readVersionFromXml()).isAtLeast(6);
+
+ Document policies = readPolicies(userId);
+ Element keepProfilesRunning = (Element) policies.getDocumentElement()
+ .getElementsByTagName("keep-profiles-running").item(0);
+ assertThat(keepProfilesRunning.getAttribute("value")).isEqualTo("true");
+ }
+
+ @Test
+ public void testEffectiveKeepProfilesRunningTrueInV5() throws Exception {
+ writeVersionToXml(5);
+
+ final int userId = UserHandle.USER_SYSTEM;
+ mProvider.mUsers = new int[]{userId};
+ preparePoliciesFile(userId, "device_policies_keep_profiles_running_true.xml");
+
+ mUpgrader.upgradePolicy(6);
+
+ assertThat(readVersionFromXml()).isAtLeast(6);
+
+ Document policies = readPolicies(userId);
+ Element keepProfilesRunning = (Element) policies.getDocumentElement()
+ .getElementsByTagName("keep-profiles-running").item(0);
+ assertThat(keepProfilesRunning.getAttribute("value")).isEqualTo("true");
+ }
+
+ @Test
+ public void testEffectiveKeepProfilesRunningFalseInV5() throws Exception {
+ writeVersionToXml(5);
+
+ final int userId = UserHandle.USER_SYSTEM;
+ mProvider.mUsers = new int[]{userId};
+ preparePoliciesFile(userId, "device_policies_keep_profiles_running_false.xml");
+
+ mUpgrader.upgradePolicy(6);
+
+ assertThat(readVersionFromXml()).isAtLeast(6);
+
+ Document policies = readPolicies(userId);
+ Element keepProfilesRunning = (Element) policies.getDocumentElement()
+ .getElementsByTagName("keep-profiles-running").item(0);
+
+ // Default value (false) is not serialized.
+ assertThat(keepProfilesRunning).isNull();
+ }
+
+
+ @Test
public void isLatestVersionTested() {
assertThat(DevicePolicyManagerService.DPMS_VERSION).isEqualTo(LATEST_TESTED_VERSION);
}
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 962e86776ea2..a6acd60f3bd7 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -85,7 +85,7 @@ public class AutomaticBrightnessControllerTest {
@Mock HysteresisLevels mAmbientBrightnessThresholdsIdle;
@Mock HysteresisLevels mScreenBrightnessThresholdsIdle;
@Mock Handler mNoOpHandler;
- @Mock HighBrightnessModeController mHbmController;
+ @Mock BrightnessRangeController mBrightnessRangeController;
@Mock BrightnessThrottler mBrightnessThrottler;
@Before
@@ -134,12 +134,15 @@ public class AutomaticBrightnessControllerTest {
DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG,
mAmbientBrightnessThresholds, mScreenBrightnessThresholds,
mAmbientBrightnessThresholdsIdle, mScreenBrightnessThresholdsIdle,
- mContext, mHbmController, mBrightnessThrottler, mIdleBrightnessMappingStrategy,
- AMBIENT_LIGHT_HORIZON_SHORT, AMBIENT_LIGHT_HORIZON_LONG, userLux, userBrightness
+ mContext, mBrightnessRangeController, mBrightnessThrottler,
+ mIdleBrightnessMappingStrategy, AMBIENT_LIGHT_HORIZON_SHORT,
+ AMBIENT_LIGHT_HORIZON_LONG, userLux, userBrightness
);
- when(mHbmController.getCurrentBrightnessMax()).thenReturn(BRIGHTNESS_MAX_FLOAT);
- when(mHbmController.getCurrentBrightnessMin()).thenReturn(BRIGHTNESS_MIN_FLOAT);
+ when(mBrightnessRangeController.getCurrentBrightnessMax()).thenReturn(
+ BRIGHTNESS_MAX_FLOAT);
+ when(mBrightnessRangeController.getCurrentBrightnessMin()).thenReturn(
+ BRIGHTNESS_MIN_FLOAT);
// Disable brightness throttling by default. Individual tests can enable it as needed.
when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
when(mBrightnessThrottler.isThrottled()).thenReturn(false);
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 5837b21b89fd..708421d2a431 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -52,6 +52,7 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -376,6 +377,116 @@ public final class DisplayDeviceConfigTest {
assertEquals(90, testMap.get(Temperature.THROTTLING_EMERGENCY).max, SMALL_DELTA);
}
+ @Test
+ public void testValidLuxThrottling() throws Exception {
+ setupDisplayDeviceConfigFromDisplayConfigFile();
+
+ Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData =
+ mDisplayDeviceConfig.getLuxThrottlingData();
+ assertEquals(2, luxThrottlingData.size());
+
+ Map<Float, Float> adaptiveOnBrightnessPoints = luxThrottlingData.get(
+ DisplayDeviceConfig.BrightnessLimitMapType.ADAPTIVE);
+ assertEquals(2, adaptiveOnBrightnessPoints.size());
+ assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA);
+ assertEquals(0.5f, adaptiveOnBrightnessPoints.get(5000f), SMALL_DELTA);
+
+ Map<Float, Float> adaptiveOffBrightnessPoints = luxThrottlingData.get(
+ DisplayDeviceConfig.BrightnessLimitMapType.DEFAULT);
+ assertEquals(2, adaptiveOffBrightnessPoints.size());
+ assertEquals(0.35f, adaptiveOffBrightnessPoints.get(1500f), SMALL_DELTA);
+ assertEquals(0.55f, adaptiveOffBrightnessPoints.get(5500f), SMALL_DELTA);
+ }
+
+ @Test
+ public void testInvalidLuxThrottling() throws Exception {
+ setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getInvalidLuxThrottling()));
+
+ Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData =
+ mDisplayDeviceConfig.getLuxThrottlingData();
+ assertEquals(1, luxThrottlingData.size());
+
+ Map<Float, Float> adaptiveOnBrightnessPoints = luxThrottlingData.get(
+ DisplayDeviceConfig.BrightnessLimitMapType.ADAPTIVE);
+ assertEquals(1, adaptiveOnBrightnessPoints.size());
+ assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA);
+ }
+
+ private String getValidLuxThrottling() {
+ return "<luxThrottling>\n"
+ + " <brightnessLimitMap>\n"
+ + " <type>adaptive</type>\n"
+ + " <map>\n"
+ + " <point>"
+ + " <first>1000</first>\n"
+ + " <second>0.3</second>\n"
+ + " </point>"
+ + " <point>"
+ + " <first>5000</first>\n"
+ + " <second>0.5</second>\n"
+ + " </point>"
+ + " </map>\n"
+ + " </brightnessLimitMap>\n"
+ + " <brightnessLimitMap>\n"
+ + " <type>default</type>\n"
+ + " <map>\n"
+ + " <point>"
+ + " <first>1500</first>\n"
+ + " <second>0.35</second>\n"
+ + " </point>"
+ + " <point>"
+ + " <first>5500</first>\n"
+ + " <second>0.55</second>\n"
+ + " </point>"
+ + " </map>\n"
+ + " </brightnessLimitMap>\n"
+ + "</luxThrottling>";
+ }
+
+ private String getInvalidLuxThrottling() {
+ return "<luxThrottling>\n"
+ + " <brightnessLimitMap>\n"
+ + " <type>adaptive</type>\n"
+ + " <map>\n"
+ + " <point>"
+ + " <first>1000</first>\n"
+ + " <second>0.3</second>\n"
+ + " </point>"
+ + " <point>" // second > hbm.transitionPoint, skipped
+ + " <first>1500</first>\n"
+ + " <second>0.9</second>\n"
+ + " </point>"
+ + " <point>" // same lux value, skipped
+ + " <first>1000</first>\n"
+ + " <second>0.5</second>\n"
+ + " </point>"
+ + " </map>\n"
+ + " </brightnessLimitMap>\n"
+ + " <brightnessLimitMap>\n" // Same type, skipped
+ + " <type>adaptive</type>\n"
+ + " <map>\n"
+ + " <point>"
+ + " <first>2000</first>\n"
+ + " <second>0.35</second>\n"
+ + " </point>"
+ + " <point>"
+ + " <first>6000</first>\n"
+ + " <second>0.55</second>\n"
+ + " </point>"
+ + " </map>\n"
+ + " </brightnessLimitMap>\n"
+ + " <brightnessLimitMap>\n" // Invalid points only, skipped
+ + " <type>default</type>\n"
+ + " <map>\n"
+ + " <point>"
+ + " <first>2500</first>\n"
+ + " <second>0.99</second>\n"
+ + " </point>"
+ + " </map>\n"
+ + " </brightnessLimitMap>\n"
+ + "</luxThrottling>";
+ }
+
private String getRefreshThermalThrottlingMaps() {
return "<refreshRateThrottlingMap>\n"
+ " <refreshRateThrottlingPoint>\n"
@@ -405,6 +516,10 @@ public final class DisplayDeviceConfigTest {
}
private String getContent() {
+ return getContent(getValidLuxThrottling());
+ }
+
+ private String getContent(String brightnessCapConfig) {
return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ "<displayConfiguration>\n"
+ "<name>Example Display</name>"
@@ -462,6 +577,7 @@ public final class DisplayDeviceConfigTest {
+ "</point>\n"
+ "</sdrHdrRatioMap>\n"
+ "</highBrightnessMode>\n"
+ + brightnessCapConfig
+ "<screenOffBrightnessSensor>\n"
+ "<type>sensor_12345</type>\n"
+ "<name>Sensor 12345</name>\n"
@@ -731,8 +847,12 @@ public final class DisplayDeviceConfigTest {
}
private void setupDisplayDeviceConfigFromDisplayConfigFile() throws IOException {
+ setupDisplayDeviceConfigFromDisplayConfigFile(getContent());
+ }
+
+ private void setupDisplayDeviceConfigFromDisplayConfigFile(String content) throws IOException {
Path tempFile = Files.createTempFile("display_config", ".tmp");
- Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
+ Files.write(tempFile, content.getBytes(StandardCharsets.UTF_8));
mDisplayDeviceConfig = new DisplayDeviceConfig(mContext);
mDisplayDeviceConfig.initFromFile(tempFile.toFile());
}
diff --git a/services/tests/servicestests/src/com/android/server/display/NormalBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/NormalBrightnessModeControllerTest.java
new file mode 100644
index 000000000000..c379d6b79ee7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/NormalBrightnessModeControllerTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.server.display;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.PowerManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.annotations.Keep;
+import com.android.server.display.DisplayDeviceConfig.BrightnessLimitMapType;
+
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+@SmallTest
+@RunWith(JUnitParamsRunner.class)
+public class NormalBrightnessModeControllerTest {
+ private static final float FLOAT_TOLERANCE = 0.001f;
+
+ private final NormalBrightnessModeController mController = new NormalBrightnessModeController();
+
+ @Keep
+ private static Object[][] brightnessData() {
+ return new Object[][]{
+ // no brightness config
+ {0, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, new HashMap<>(),
+ PowerManager.BRIGHTNESS_MAX},
+ {0, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, new HashMap<>(),
+ PowerManager.BRIGHTNESS_MAX},
+ {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, new HashMap<>(),
+ PowerManager.BRIGHTNESS_MAX},
+ // Auto brightness - on, config only for default
+ {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of(
+ BrightnessLimitMapType.DEFAULT,
+ ImmutableMap.of(99f, 0.1f, 101f, 0.2f)
+ ), 0.2f},
+ // Auto brightness - off, config only for default
+ {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of(
+ BrightnessLimitMapType.DEFAULT,
+ ImmutableMap.of(99f, 0.1f, 101f, 0.2f)
+ ), 0.2f},
+ // Auto brightness - off, config only for adaptive
+ {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of(
+ BrightnessLimitMapType.ADAPTIVE,
+ ImmutableMap.of(99f, 0.1f, 101f, 0.2f)
+ ), PowerManager.BRIGHTNESS_MAX},
+ // Auto brightness - on, config only for adaptive
+ {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of(
+ BrightnessLimitMapType.ADAPTIVE,
+ ImmutableMap.of(99f, 0.1f, 101f, 0.2f)
+ ), 0.2f},
+ // Auto brightness - on, config for both
+ {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of(
+ BrightnessLimitMapType.DEFAULT,
+ ImmutableMap.of(99f, 0.1f, 101f, 0.2f),
+ BrightnessLimitMapType.ADAPTIVE,
+ ImmutableMap.of(99f, 0.3f, 101f, 0.4f)
+ ), 0.4f},
+ // Auto brightness - off, config for both
+ {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of(
+ BrightnessLimitMapType.DEFAULT,
+ ImmutableMap.of(99f, 0.1f, 101f, 0.2f),
+ BrightnessLimitMapType.ADAPTIVE,
+ ImmutableMap.of(99f, 0.3f, 101f, 0.4f)
+ ), 0.2f},
+ // Auto brightness - on, config for both, ambient high
+ {1000, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of(
+ BrightnessLimitMapType.DEFAULT,
+ ImmutableMap.of(1000f, 0.1f, 2000f, 0.2f),
+ BrightnessLimitMapType.ADAPTIVE,
+ ImmutableMap.of(99f, 0.3f, 101f, 0.4f)
+ ), PowerManager.BRIGHTNESS_MAX},
+ };
+ }
+
+ @Test
+ @Parameters(method = "brightnessData")
+ public void testReturnsCorrectMaxBrightness(float ambientLux, int autoBrightnessState,
+ Map<BrightnessLimitMapType, Map<Float, Float>> maxBrightnessConfig,
+ float expectedBrightness) {
+ setupController(ambientLux, autoBrightnessState, maxBrightnessConfig);
+
+ assertEquals(expectedBrightness, mController.getCurrentBrightnessMax(), FLOAT_TOLERANCE);
+ }
+
+ private void setupController(float ambientLux, int autoBrightnessState,
+ Map<BrightnessLimitMapType, Map<Float, Float>> maxBrightnessConfig) {
+ mController.onAmbientLuxChange(ambientLux);
+ mController.setAutoBrightnessState(autoBrightnessState);
+ mController.resetNbmData(maxBrightnessConfig);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
index aa6ee09e0179..0b13f9a35c1f 100644
--- a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
@@ -52,7 +52,7 @@ public class BatterySaverPolicyTest extends AndroidTestCase {
private static final int SOUND_TRIGGER_MODE = 0; // SOUND_TRIGGER_MODE_ALL_ENABLED
private static final int DEFAULT_SOUND_TRIGGER_MODE =
PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY;
- private static final String BATTERY_SAVER_CONSTANTS = "disable_vibration=true,"
+ private static final String BATTERY_SAVER_CONSTANTS = "disable_vibration=false,"
+ "advertise_is_enabled=true,"
+ "disable_animation=false,"
+ "enable_firewall=true,"
@@ -117,7 +117,7 @@ public class BatterySaverPolicyTest extends AndroidTestCase {
@SmallTest
public void testGetBatterySaverPolicy_PolicyVibration_DefaultValueCorrect() {
- testServiceDefaultValue_On(ServiceType.VIBRATION);
+ testServiceDefaultValue_Off(ServiceType.VIBRATION);
}
@SmallTest
@@ -211,7 +211,7 @@ public class BatterySaverPolicyTest extends AndroidTestCase {
private void verifyBatterySaverConstantsUpdated() {
final PowerSaveState vibrationState =
mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.VIBRATION);
- assertThat(vibrationState.batterySaverEnabled).isTrue();
+ assertThat(vibrationState.batterySaverEnabled).isFalse();
final PowerSaveState animationState =
mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.ANIMATION);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index cf839812f6aa..ebe40b05162d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -170,9 +170,13 @@ public class WindowProcessControllerTests extends WindowTestsBase {
}
@Test
- public void testSetRunningRecentsAnimation() {
- mWpc.setRunningRecentsAnimation(true);
- mWpc.setRunningRecentsAnimation(false);
+ public void testSetAnimatingReason() {
+ mWpc.addAnimatingReason(WindowProcessController.ANIMATING_REASON_REMOTE_ANIMATION);
+ assertTrue(mWpc.isRunningRemoteTransition());
+ mWpc.addAnimatingReason(WindowProcessController.ANIMATING_REASON_WAKEFULNESS_CHANGE);
+ mWpc.removeAnimatingReason(WindowProcessController.ANIMATING_REASON_REMOTE_ANIMATION);
+ assertFalse(mWpc.isRunningRemoteTransition());
+ mWpc.removeAnimatingReason(WindowProcessController.ANIMATING_REASON_WAKEFULNESS_CHANGE);
waitHandlerIdle(mAtm.mH);
InOrder orderVerifier = Mockito.inOrder(mMockListener);
@@ -201,7 +205,7 @@ public class WindowProcessControllerTests extends WindowTestsBase {
waitHandlerIdle(mAtm.mH);
InOrder orderVerifier = Mockito.inOrder(mMockListener);
- orderVerifier.verify(mMockListener, times(3)).setRunningRemoteAnimation(eq(true));
+ orderVerifier.verify(mMockListener, times(1)).setRunningRemoteAnimation(eq(true));
orderVerifier.verify(mMockListener, times(1)).setRunningRemoteAnimation(eq(false));
orderVerifier.verifyNoMoreInteractions();
}
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 0d88a0d485ff..030615fd7527 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -259,7 +259,24 @@ public class StorageStatsService extends IStorageStatsManager.Stub {
// NOTE: No permissions required
if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
- return FileUtils.roundStorageSize(mStorage.getPrimaryStorageSize());
+ // As a safety measure, use the original implementation for the devices
+ // with storage size <= 512GB to prevent any potential regressions
+ final long roundedUserspaceBytes = mStorage.getPrimaryStorageSize();
+ if (roundedUserspaceBytes <= DataUnit.GIGABYTES.toBytes(512)) {
+ return roundedUserspaceBytes;
+ }
+
+ // Since 1TB devices can actually have either 1000GB or 1024GB,
+ // get the block device size and do just a small rounding if any at all
+ final long totalBytes = mStorage.getInternalStorageBlockDeviceSize();
+ final long totalBytesRounded = FileUtils.roundStorageSize(totalBytes);
+ // If the storage size is 997GB-999GB, round it to a 1000GB to show
+ // 1TB in UI instead of 0.99TB. Same for 2TB, 4TB, 8TB etc.
+ if (totalBytesRounded - totalBytes <= DataUnit.GIGABYTES.toBytes(3)) {
+ return totalBytesRounded;
+ } else {
+ return totalBytes;
+ }
} else {
final VolumeInfo vol = mStorage.findVolumeByUuid(volumeUuid);
if (vol == null) {
@@ -286,15 +303,19 @@ public class StorageStatsService extends IStorageStatsManager.Stub {
// Free space is usable bytes plus any cached data that we're
// willing to automatically clear. To avoid user confusion, this
// logic should be kept in sync with getAllocatableBytes().
+ long freeBytes;
if (isQuotaSupported(volumeUuid, PLATFORM_PACKAGE_NAME)) {
final long cacheTotal = getCacheBytes(volumeUuid, PLATFORM_PACKAGE_NAME);
final long cacheReserved = mStorage.getStorageCacheBytes(path, 0);
final long cacheClearable = Math.max(0, cacheTotal - cacheReserved);
- return path.getUsableSpace() + cacheClearable;
+ freeBytes = path.getUsableSpace() + cacheClearable;
} else {
- return path.getUsableSpace();
+ freeBytes = path.getUsableSpace();
}
+
+ Slog.d(TAG, "getFreeBytes: " + freeBytes);
+ return freeBytes;
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 64e43568e4d6..8e4ec0914563 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -151,6 +151,15 @@ public class SubscriptionManager {
"restoreSimSpecificSettings";
/**
+ * The key of the boolean flag indicating whether restoring subscriptions actually changes
+ * the subscription database or not.
+ *
+ * @hide
+ */
+ public static final String RESTORE_SIM_SPECIFIC_SETTINGS_DATABASE_UPDATED =
+ "restoreSimSpecificSettingsDatabaseUpdated";
+
+ /**
* Key to the backup & restore data byte array in the Bundle that is returned by {@link
* #getAllSimSpecificSettingsForBackup()} or to be pass in to {@link
* #restoreAllSimSpecificSettingsFromBackup(byte[])}.
diff --git a/tests/UiBench/Android.bp b/tests/UiBench/Android.bp
index 90e61c52da68..0d2f2ef46cab 100644
--- a/tests/UiBench/Android.bp
+++ b/tests/UiBench/Android.bp
@@ -24,6 +24,5 @@ android_test {
"androidx.recyclerview_recyclerview",
"androidx.leanback_leanback",
],
- certificate: "platform",
test_suites: ["device-tests"],
}
diff --git a/tests/UiBench/AndroidManifest.xml b/tests/UiBench/AndroidManifest.xml
index 47211c5fbad1..4fc6ec71f29c 100644
--- a/tests/UiBench/AndroidManifest.xml
+++ b/tests/UiBench/AndroidManifest.xml
@@ -18,7 +18,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.android.test.uibench">
- <uses-permission android:name="android.permission.INJECT_EVENTS" />
<application android:allowBackup="false"
android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
diff --git a/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java b/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
index 1b2c3c60ffd4..06b65a7f9bbf 100644
--- a/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
@@ -15,15 +15,11 @@
*/
package com.android.test.uibench;
-import android.app.Instrumentation;
+import android.content.Intent;
import android.os.Bundle;
-import android.os.Looper;
-import android.os.MessageQueue;
-import androidx.appcompat.app.AppCompatActivity;
-import android.view.KeyEvent;
import android.widget.EditText;
-import java.util.concurrent.Semaphore;
+import androidx.appcompat.app.AppCompatActivity;
/**
* Note: currently incomplete, complexity of input continuously grows, instead of looping
@@ -32,7 +28,13 @@ import java.util.concurrent.Semaphore;
* Simulates typing continuously into an EditText.
*/
public class EditTextTypeActivity extends AppCompatActivity {
- Thread mThread;
+
+ /**
+ * Broadcast action: Used to notify UiBenchEditTextTypingMicrobenchmark test when the
+ * test activity was paused.
+ */
+ private static final String ACTION_CANCEL_TYPING_CALLBACK =
+ "com.android.uibench.action.CANCEL_TYPING_CALLBACK";
private static String sSeedText = "";
static {
@@ -46,9 +48,6 @@ public class EditTextTypeActivity extends AppCompatActivity {
sSeedText = builder.toString();
}
- final Object mLock = new Object();
- boolean mShouldStop = false;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -56,55 +55,13 @@ public class EditTextTypeActivity extends AppCompatActivity {
EditText editText = new EditText(this);
editText.setText(sSeedText);
setContentView(editText);
-
- final Instrumentation instrumentation = new Instrumentation();
- final Semaphore sem = new Semaphore(0);
- MessageQueue.IdleHandler handler = new MessageQueue.IdleHandler() {
- @Override
- public boolean queueIdle() {
- // TODO: consider other signaling approaches
- sem.release();
- return true;
- }
- };
- Looper.myQueue().addIdleHandler(handler);
- synchronized (mLock) {
- mShouldStop = false;
- }
- mThread = new Thread(new Runnable() {
- int codes[] = { KeyEvent.KEYCODE_H, KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_L,
- KeyEvent.KEYCODE_L, KeyEvent.KEYCODE_O, KeyEvent.KEYCODE_SPACE };
- int i = 0;
- @Override
- public void run() {
- while (true) {
- try {
- sem.acquire();
- } catch (InterruptedException e) {
- // TODO, maybe
- }
- int code = codes[i % codes.length];
- if (i % 100 == 99) code = KeyEvent.KEYCODE_ENTER;
-
- synchronized (mLock) {
- if (mShouldStop) break;
- }
-
- // TODO: bit of a race here, since the event can arrive after pause/stop.
- // (Can't synchronize on key send, since it's synchronous.)
- instrumentation.sendKeyDownUpSync(code);
- i++;
- }
- }
- });
- mThread.start();
}
@Override
protected void onPause() {
- synchronized (mLock) {
- mShouldStop = true;
- }
+ // Cancel the typing when the test activity was paused.
+ sendBroadcast(new Intent(ACTION_CANCEL_TYPING_CALLBACK).addFlags(
+ Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY));
super.onPause();
}
}