summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt1
-rw-r--r--core/api/system-current.txt6
-rw-r--r--core/api/test-current.txt1
-rw-r--r--core/java/android/app/ApplicationPackageManager.java89
-rw-r--r--core/java/android/app/StatusBarManager.java2
-rw-r--r--core/java/android/app/SystemServiceRegistry.java2
-rw-r--r--core/java/android/app/WindowConfiguration.java9
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java1
-rw-r--r--core/java/android/content/res/Resources.java5
-rw-r--r--core/java/android/hardware/biometrics/BiometricManager.java6
-rw-r--r--core/java/android/hardware/biometrics/IBiometricService.aidl2
-rw-r--r--core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl2
-rw-r--r--core/java/android/os/AppZygote.java15
-rwxr-xr-xcore/java/android/os/Build.java11
-rw-r--r--core/java/android/provider/Settings.java22
-rw-r--r--core/java/android/util/FeatureFlagUtils.java2
-rw-r--r--core/java/android/util/NtpTrustedTime.java7
-rw-r--r--core/java/android/view/IWindowSession.aidl3
-rw-r--r--core/java/android/view/InsetsSourceConsumer.java26
-rw-r--r--core/java/android/view/SurfaceView.java203
-rw-r--r--core/java/android/view/SyncRtSurfaceTransactionApplier.java8
-rw-r--r--core/java/android/view/View.java64
-rw-r--r--core/java/android/view/ViewRootImpl.java24
-rw-r--r--core/java/android/view/WindowlessWindowManager.java7
-rw-r--r--core/java/android/view/accessibility/IWindowMagnificationConnection.aidl22
-rw-r--r--core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl6
-rw-r--r--core/java/android/webkit/WebViewZygote.java5
-rw-r--r--core/java/android/widget/inline/InlineContentView.java5
-rw-r--r--core/java/android/window/SplashScreenView.java11
-rw-r--r--core/java/android/window/WindowOnBackInvokedDispatcher.java2
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java35
-rw-r--r--core/java/com/android/internal/os/Zygote.java263
-rw-r--r--core/java/com/android/internal/policy/ForceShowNavigationBarSettingsObserver.java6
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl2
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBarService.aidl2
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp6
-rw-r--r--core/proto/android/providers/settings/secure.proto15
-rw-r--r--core/proto/android/server/biometrics.proto14
-rw-r--r--core/proto/android/server/windowmanagerservice.proto1
-rw-r--r--core/res/AndroidManifest.xml4
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java49
-rw-r--r--graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java27
-rw-r--r--graphics/java/android/graphics/drawable/AnimationDrawable.java19
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java111
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java150
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java4
-rw-r--r--media/jni/soundpool/Stream.cpp94
-rw-r--r--media/jni/soundpool/Stream.h33
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java72
-rw-r--r--native/android/performance_hint.cpp4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java239
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java4
-rw-r--r--packages/SettingsLib/tests/integ/AndroidManifest.xml11
-rw-r--r--packages/SettingsLib/tests/integ/res/xml/file_paths.xml20
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java299
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java5
-rw-r--r--packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml255
-rw-r--r--packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml1
-rw-r--r--packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml248
-rw-r--r--packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml1
-rw-r--r--packages/SystemUI/res/layout/auth_biometric_contents.xml4
-rw-r--r--packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml (renamed from packages/SystemUI/res/layout/auth_biometric_face_to_fingerprint_view.xml)5
-rw-r--r--packages/SystemUI/res/layout/auth_biometric_view.xml (renamed from packages/SystemUI/res/layout/auth_biometric_udfps_view.xml)6
-rw-r--r--packages/SystemUI/res/values/dimens.xml3
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt123
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java245
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java261
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.kt80
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt112
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java174
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt92
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt94
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java80
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java304
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java346
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java66
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/Utils.java130
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt120
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java193
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java54
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java142
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java84
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java344
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java119
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java360
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java329
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt324
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java59
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt14
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java3
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java13
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java30
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java4
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java19
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java148
-rw-r--r--services/core/java/android/content/pm/PackageManagerInternal.java5
-rw-r--r--services/core/java/com/android/server/BinaryTransparencyService.java86
-rw-r--r--services/core/java/com/android/server/NetworkTimeUpdateService.java63
-rw-r--r--services/core/java/com/android/server/NetworkTimeUpdateServiceShellCommand.java90
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java8
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java19
-rw-r--r--services/core/java/com/android/server/am/HostingRecord.java38
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java220
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java35
-rw-r--r--services/core/java/com/android/server/app/GameManagerService.java46
-rw-r--r--services/core/java/com/android/server/biometrics/AuthSession.java170
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricSensor.java6
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricService.java490
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java7
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java65
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/RemovalClient.java19
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java2
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java6
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java4
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java8
-rw-r--r--services/core/java/com/android/server/pm/PackageHandler.java9
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java44
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceUtils.java11
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java4
-rw-r--r--services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java3
-rw-r--r--services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java3
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java12
-rw-r--r--services/core/java/com/android/server/power/hint/HintManagerService.java60
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java2
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java13
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java17
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java4
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java2
-rw-r--r--services/core/java/com/android/server/wm/ConfigurationContainer.java30
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java25
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java6
-rw-r--r--services/core/java/com/android/server/wm/DragResizeMode.java2
-rw-r--r--services/core/java/com/android/server/wm/InsetsPolicy.java5
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimationController.java4
-rw-r--r--services/core/java/com/android/server/wm/Session.java10
-rw-r--r--services/core/java/com/android/server/wm/Task.java11
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java96
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java176
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java342
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java401
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java124
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java65
-rw-r--r--test-runner/src/android/test/IsolatedContext.java11
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt1
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt3
-rw-r--r--tests/TrustTests/Android.bp39
-rw-r--r--tests/TrustTests/AndroidManifest.xml75
-rw-r--r--tests/TrustTests/AndroidTest.xml28
-rw-r--r--tests/TrustTests/README.md40
-rw-r--r--tests/TrustTests/src/android/trust/BaseTrustAgentService.kt47
-rw-r--r--tests/TrustTests/src/android/trust/TrustTestActivity.kt30
-rw-r--r--tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt95
-rw-r--r--tests/TrustTests/src/android/trust/test/LockUserTest.kt68
-rw-r--r--tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt81
-rw-r--r--tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt83
-rw-r--r--tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt105
-rw-r--r--tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt117
216 files changed, 6300 insertions, 5117 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index cd0bd8699794..d8ea767a6400 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -30867,6 +30867,7 @@ package android.os {
field public static final int PREVIEW_SDK_INT;
field public static final String RELEASE;
field @NonNull public static final String RELEASE_OR_CODENAME;
+ field @NonNull public static final String RELEASE_OR_PREVIEW_DISPLAY;
field @Deprecated public static final String SDK;
field public static final int SDK_INT;
field public static final String SECURITY_PATCH;
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index e34498736467..083269c7c14f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -10524,6 +10524,7 @@ package android.provider {
field public static final String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
field public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI";
field public static final String ACTION_TETHER_SETTINGS = "android.settings.TETHER_SETTINGS";
+ field public static final String ACTION_TETHER_UNSUPPORTED_CARRIER_UI = "android.settings.TETHER_UNSUPPORTED_CARRIER_UI";
}
public static final class Settings.Global extends android.provider.Settings.NameValueTable {
@@ -15701,6 +15702,11 @@ package android.util {
package android.view {
+ @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
+ method @NonNull public final java.util.List<android.graphics.Rect> getUnrestrictedPreferKeepClearRects();
+ method @RequiresPermission(android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS) public final void setUnrestrictedPreferKeepClearRects(@NonNull java.util.List<android.graphics.Rect>);
+ }
+
public abstract class Window {
method public void addSystemFlags(@android.view.WindowManager.LayoutParams.SystemFlags int);
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index ed18a2219915..c36b94b424ac 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -516,6 +516,7 @@ package android.app.admin {
method public boolean isCurrentInputMethodSetByOwner();
method public boolean isFactoryResetProtectionPolicySupported();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean isNewUserDisclaimerAcknowledged();
+ method public boolean isRemovingAdmin(@NonNull android.content.ComponentName, int);
method @RequiresPermission(anyOf={android.Manifest.permission.MARK_DEVICE_ORGANIZATION_OWNED, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}, conditional=true) public void markProfileOwnerOnOrganizationOwnedDevice(@NonNull android.content.ComponentName);
method @NonNull public static String operationSafetyReasonToString(int);
method @NonNull public static String operationToString(int);
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index bda2e459f52a..dca5c542af17 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -165,50 +165,35 @@ public class ApplicationPackageManager extends PackageManager {
public static final String PERMISSION_CONTROLLER_RESOURCE_PACKAGE =
"com.android.permissioncontroller";
- private final Object mLock = new Object();
-
- @GuardedBy("mLock")
- private UserManager mUserManager;
- @GuardedBy("mLock")
- private PermissionManager mPermissionManager;
- @GuardedBy("mLock")
- private PackageInstaller mInstaller;
- @GuardedBy("mLock")
- private ArtManager mArtManager;
- @GuardedBy("mLock")
- private DevicePolicyManager mDevicePolicyManager;
+ private volatile UserManager mUserManager;
+ private volatile PermissionManager mPermissionManager;
+ private volatile PackageInstaller mInstaller;
+ private volatile ArtManager mArtManager;
+ private volatile DevicePolicyManager mDevicePolicyManager;
+ private volatile String mPermissionsControllerPackageName;
@GuardedBy("mDelegates")
private final ArrayList<MoveCallbackDelegate> mDelegates = new ArrayList<>();
- @GuardedBy("mLock")
- private String mPermissionsControllerPackageName;
-
UserManager getUserManager() {
- synchronized (mLock) {
- if (mUserManager == null) {
- mUserManager = UserManager.get(mContext);
- }
- return mUserManager;
+ if (mUserManager == null) {
+ mUserManager = UserManager.get(mContext);
}
+ return mUserManager;
}
DevicePolicyManager getDevicePolicyManager() {
- synchronized (mLock) {
- if (mDevicePolicyManager == null) {
- mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
- }
- return mDevicePolicyManager;
+ if (mDevicePolicyManager == null) {
+ mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
}
+ return mDevicePolicyManager;
}
private PermissionManager getPermissionManager() {
- synchronized (mLock) {
- if (mPermissionManager == null) {
- mPermissionManager = mContext.getSystemService(PermissionManager.class);
- }
- return mPermissionManager;
+ if (mPermissionManager == null) {
+ mPermissionManager = mContext.getSystemService(PermissionManager.class);
}
+ return mPermissionManager;
}
@Override
@@ -851,16 +836,14 @@ public class ApplicationPackageManager extends PackageManager {
*/
@Override
public String getPermissionControllerPackageName() {
- synchronized (mLock) {
- if (mPermissionsControllerPackageName == null) {
- try {
- mPermissionsControllerPackageName = mPM.getPermissionControllerPackageName();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ if (mPermissionsControllerPackageName == null) {
+ try {
+ mPermissionsControllerPackageName = mPM.getPermissionControllerPackageName();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
- return mPermissionsControllerPackageName;
}
+ return mPermissionsControllerPackageName;
}
/**
@@ -3235,17 +3218,15 @@ public class ApplicationPackageManager extends PackageManager {
@Override
public PackageInstaller getPackageInstaller() {
- synchronized (mLock) {
- if (mInstaller == null) {
- try {
- mInstaller = new PackageInstaller(mPM.getPackageInstaller(),
- mContext.getPackageName(), mContext.getAttributionTag(), getUserId());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ if (mInstaller == null) {
+ try {
+ mInstaller = new PackageInstaller(mPM.getPackageInstaller(),
+ mContext.getPackageName(), mContext.getAttributionTag(), getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
- return mInstaller;
}
+ return mInstaller;
}
@Override
@@ -3583,16 +3564,14 @@ public class ApplicationPackageManager extends PackageManager {
@Override
public ArtManager getArtManager() {
- synchronized (mLock) {
- if (mArtManager == null) {
- try {
- mArtManager = new ArtManager(mContext, mPM.getArtManager());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ if (mArtManager == null) {
+ try {
+ mArtManager = new ArtManager(mContext, mPM.getArtManager());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
- return mArtManager;
}
+ return mArtManager;
}
@Override
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index e460638d4420..c6e36a36701b 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -335,7 +335,7 @@ public class StatusBarManager {
* Constant for {@link #setNavBarMode(int)} indicating kids navbar mode.
*
* <p>When used, back and home icons will change drawables and layout, recents will be hidden,
- * and the navbar will remain visible when apps are in immersive mode.
+ * and enables the setting to force navbar visible, even when apps are in immersive mode.
*
* @hide
*/
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index ddce29da8e00..58db93c123bb 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -18,6 +18,7 @@ package android.app;
import android.accounts.AccountManager;
import android.accounts.IAccountManager;
+import android.adservices.AdServicesFrameworkInitializer;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -1565,6 +1566,7 @@ public final class SystemServiceRegistry {
RoleFrameworkInitializer.registerServiceWrappers();
SchedulingFrameworkInitializer.registerServiceWrappers();
SdkSandboxManagerFrameworkInitializer.registerServiceWrappers();
+ AdServicesFrameworkInitializer.registerServiceWrappers();
UwbFrameworkInitializer.registerServiceWrappers();
SafetyCenterFrameworkInitializer.registerServiceWrappers();
ConnectivityFrameworkInitializerTiramisu.registerServiceWrappers();
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index b791f05c1076..5c1ab3879487 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -841,15 +841,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
}
/**
- * Returns true if the windowingMode represents a split window.
- * @hide
- */
- public static boolean isSplitScreenWindowingMode(int windowingMode) {
- return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
- }
-
- /**
* Returns true if the windows associated with this window configuration can receive input keys.
* @hide
*/
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index d5d14c6c0fbb..a2df2d029059 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3766,6 +3766,7 @@ public class DevicePolicyManager {
* for the user.
* @hide
*/
+ @TestApi
public boolean isRemovingAdmin(@NonNull ComponentName admin, int userId) {
if (mService != null) {
try {
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index ebef0535f077..a03286d3ec6f 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -2674,9 +2674,8 @@ public class Resources {
// Putting into a map keyed on the apk assets to deduplicate resources that are different
// objects but ultimately represent the same assets
Map<List<ApkAssets>, Resources> history = new ArrayMap<>();
- for (Resources r : sResourcesHistory) {
- history.put(Arrays.asList(r.mResourcesImpl.mAssets.getApkAssets()), r);
- }
+ sResourcesHistory.forEach(
+ r -> history.put(Arrays.asList(r.mResourcesImpl.mAssets.getApkAssets()), r));
int i = 0;
for (Resources r : history.values()) {
if (r != null) {
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index ada51559a38d..3f139f02efaf 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -104,16 +104,16 @@ public class BiometricManager {
public static final int BIOMETRIC_MULTI_SENSOR_DEFAULT = 0;
/**
- * Prefer the face sensor and fall back to fingerprint when needed.
+ * Use face and fingerprint sensors together.
* @hide
*/
- public static final int BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT = 1;
+ public static final int BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE = 1;
/**
* @hide
*/
@IntDef({BIOMETRIC_MULTI_SENSOR_DEFAULT,
- BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT})
+ BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE})
@Retention(RetentionPolicy.SOURCE)
public @interface BiometricMultiSensorMode {}
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index 2c3c8c353e8c..42aad36e44c1 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -63,7 +63,7 @@ interface IBiometricService {
// Notify BiometricService when <Biometric>Service is ready to start the prepared client.
// Client lifecycle is still managed in <Biometric>Service.
- void onReadyForAuthentication(int cookie);
+ void onReadyForAuthentication(long requestId, int cookie);
// Requests all BIOMETRIC_STRONG sensors to have their authenticatorId invalidated for the
// specified user. This happens when enrollments have been added on devices with multiple
diff --git a/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl
index 5d9b5f3bcc05..450c5ceab04c 100644
--- a/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl
@@ -30,6 +30,4 @@ oneway interface IBiometricSysuiReceiver {
void onSystemEvent(int event);
// Notifies that the dialog has finished animating.
void onDialogAnimatedIn();
- // For multi-sensor devices, notifies that the fingerprint should start now.
- void onStartFingerprintNow();
}
diff --git a/core/java/android/os/AppZygote.java b/core/java/android/os/AppZygote.java
index c8b4226ecae0..07fbe4a04ff1 100644
--- a/core/java/android/os/AppZygote.java
+++ b/core/java/android/os/AppZygote.java
@@ -17,9 +17,11 @@
package android.os;
import android.content.pm.ApplicationInfo;
+import android.content.pm.ProcessInfo;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.Zygote;
import dalvik.system.VMRuntime;
@@ -45,8 +47,6 @@ public class AppZygote {
// Last UID/GID of the range the AppZygote can setuid()/setgid() to
private final int mZygoteUidGidMax;
- private final int mZygoteRuntimeFlags;
-
private final Object mLock = new Object();
/**
@@ -57,14 +57,15 @@ public class AppZygote {
private ChildZygoteProcess mZygote;
private final ApplicationInfo mAppInfo;
+ private final ProcessInfo mProcessInfo;
- public AppZygote(ApplicationInfo appInfo, int zygoteUid, int uidGidMin, int uidGidMax,
- int runtimeFlags) {
+ public AppZygote(ApplicationInfo appInfo, ProcessInfo processInfo, int zygoteUid, int uidGidMin,
+ int uidGidMax) {
mAppInfo = appInfo;
+ mProcessInfo = processInfo;
mZygoteUid = zygoteUid;
mZygoteUidGidMin = uidGidMin;
mZygoteUidGidMax = uidGidMax;
- mZygoteRuntimeFlags = runtimeFlags;
}
/**
@@ -108,13 +109,15 @@ public class AppZygote {
String abi = mAppInfo.primaryCpuAbi != null ? mAppInfo.primaryCpuAbi :
Build.SUPPORTED_ABIS[0];
try {
+ int runtimeFlags = Zygote.getMemorySafetyRuntimeFlagsForSecondaryZygote(
+ mAppInfo, mProcessInfo);
mZygote = Process.ZYGOTE_PROCESS.startChildZygote(
"com.android.internal.os.AppZygoteInit",
mAppInfo.processName + "_zygote",
mZygoteUid,
mZygoteUid,
null, // gids
- mZygoteRuntimeFlags, // runtimeFlags
+ runtimeFlags,
"app_zygote", // seInfo
abi, // abi
abi, // acceptedAbiList
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 1d1f17df17df..1c85f692b232 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -285,13 +285,20 @@ public class Build {
public static final String RELEASE = getString("ro.build.version.release");
/**
- * The version string we show to the user; may be {@link #RELEASE} or
- * {@link #CODENAME} if not a final release build.
+ * The version string. May be {@link #RELEASE} or {@link #CODENAME} if
+ * not a final release build.
*/
@NonNull public static final String RELEASE_OR_CODENAME = getString(
"ro.build.version.release_or_codename");
/**
+ * The version string we show to the user; may be {@link #RELEASE} or
+ * a descriptive string if not a final release build.
+ */
+ @NonNull public static final String RELEASE_OR_PREVIEW_DISPLAY = getString(
+ "ro.build.version.release_or_preview_display");
+
+ /**
* The base OS build the product is based on.
*/
public static final String BASE_OS = SystemProperties.get("ro.build.version.base_os", "");
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 8feff16c70a2..f2137b30350a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -240,6 +240,20 @@ public final class Settings {
"android.settings.TETHER_PROVISIONING_UI";
/**
+ * Activity Action: Show a dialog activity to notify tethering is NOT supported by carrier.
+ *
+ * When {@link android.telephony.CarrierConfigManager#KEY_CARRIER_SUPPORTS_TETHERING_BOOL}
+ * is false, and tethering is started by Settings, this dialog activity will be started to
+ * tell the user that tethering is not supported by carrier.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @SystemApi
+ public static final String ACTION_TETHER_UNSUPPORTED_CARRIER_UI =
+ "android.settings.TETHER_UNSUPPORTED_CARRIER_UI";
+
+ /**
* Activity Action: Show settings to allow entering/exiting airplane mode.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -10274,6 +10288,14 @@ public final class Settings {
"theme_customization_overlay_packages";
/**
+ * Indicates whether the nav bar is forced to always be visible, even in immersive mode.
+ * <p>Type: int (0 for false, 1 for true)
+ *
+ * @hide
+ */
+ public static final String NAV_BAR_FORCE_VISIBLE = "nav_bar_force_visible";
+
+ /**
* Indicates whether the device is in kids nav mode.
* <p>Type: int (0 for false, 1 for true)
*
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 188bc3f51c52..4541f3afa428 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -95,7 +95,7 @@ public class FeatureFlagUtils {
DEFAULT_FLAGS.put(SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES, "true");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SECURITY_HUB, "true");
DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "true");
- DEFAULT_FLAGS.put("settings_search_always_expand", "false");
+ DEFAULT_FLAGS.put("settings_search_always_expand", "true");
DEFAULT_FLAGS.put(SETTINGS_APP_LANGUAGE_SELECTION, "true");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "false");
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index 8604078b5ae5..40beab323576 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -265,6 +265,13 @@ public class NtpTrustedTime implements TrustedTime {
return mTimeResult;
}
+ /** Clears the last received NTP. Intended for use during tests. */
+ public void clearCachedTimeResult() {
+ synchronized (this) {
+ mTimeResult = null;
+ }
+ }
+
private static class NtpConnectionInfo {
@NonNull private final String mServer;
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index a266a28dcaca..5c7c844ae773 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -290,7 +290,8 @@ interface IWindowSession {
/**
* Called when the keep-clear areas for this window have changed.
*/
- oneway void reportKeepClearAreasChanged(IWindow window, in List<Rect> keepClearRects);
+ oneway void reportKeepClearAreasChanged(IWindow window, in List<Rect> restricted,
+ in List<Rect> unrestricted);
/**
* Request the server to call setInputWindowInfo on a given Surface, and return
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index b1582cf9f023..6aab6359d23e 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -98,6 +98,13 @@ public class InsetsSourceConsumer {
*/
private boolean mIsAnimationPending;
+ /**
+ * @param type The {@link InternalInsetsType} of the consumed insets.
+ * @param state The current {@link InsetsState} of the consumed insets.
+ * @param transactionSupplier The source of new {@link Transaction} instances. The supplier
+ * must provide *new* instances, which will be explicitly closed by this class.
+ * @param controller The {@link InsetsController} to use for insets interaction.
+ */
public InsetsSourceConsumer(@InternalInsetsType int type, InsetsState state,
Supplier<Transaction> transactionSupplier, InsetsController controller) {
mType = type;
@@ -390,16 +397,17 @@ public class InsetsSourceConsumer {
return;
}
- final Transaction t = mTransactionSupplier.get();
- if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + mRequestedVisible);
- if (mRequestedVisible) {
- t.show(mSourceControl.getLeash());
- } else {
- t.hide(mSourceControl.getLeash());
+ try (Transaction t = mTransactionSupplier.get()) {
+ if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + mRequestedVisible);
+ if (mRequestedVisible) {
+ t.show(mSourceControl.getLeash());
+ } else {
+ t.hide(mSourceControl.getLeash());
+ }
+ // Ensure the alpha value is aligned with the actual requested visibility.
+ t.setAlpha(mSourceControl.getLeash(), mRequestedVisible ? 1 : 0);
+ t.apply();
}
- // Ensure the alpha value is aligned with the actual requested visibility.
- t.setAlpha(mSourceControl.getLeash(), mRequestedVisible ? 1 : 0);
- t.apply();
onPerceptible(mRequestedVisible);
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 91d5b208c237..9aa243b241f7 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -133,8 +133,9 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
private boolean mDisableBackgroundLayer = false;
/**
- * We use this lock to protect access to mSurfaceControl. Both are accessed on the UI
- * thread and the render thread via RenderNode.PositionUpdateListener#positionLost.
+ * We use this lock to protect access to mSurfaceControl and
+ * SurfaceViewPositionUpdateListener#mPositionChangedTransaction. Both are accessed on the UI
+ * thread and the render thread.
*/
final Object mSurfaceControlLock = new Object();
final Rect mTmpRect = new Rect();
@@ -223,6 +224,12 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
private final SurfaceControl.Transaction mFrameCallbackTransaction =
new SurfaceControl.Transaction();
+ /**
+ * A temporary transaction holder that should only be used when applying right away. There
+ * should be no assumption about thread safety for this transaction.
+ */
+ private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
+
private int mParentSurfaceSequenceId;
private RemoteAccessibilityController mRemoteAccessibilityController =
@@ -753,7 +760,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
mBlastBufferQueue = null;
}
- final Transaction transaction = new Transaction();
+ Transaction transaction = new Transaction();
if (mSurfaceControl != null) {
transaction.remove(mSurfaceControl);
mSurfaceControl = null;
@@ -783,18 +790,22 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
// synchronously otherwise we may see flickers.
// When the listener is updated, we will get at least a single position update call so we can
// guarantee any changes we post will be applied.
- private void replacePositionUpdateListener(int surfaceWidth, int surfaceHeight) {
+ private void replacePositionUpdateListener(int surfaceWidth, int surfaceHeight,
+ Transaction geometryTransaction) {
if (mPositionListener != null) {
mRenderNode.removePositionUpdateListener(mPositionListener);
+ synchronized (mSurfaceControlLock) {
+ geometryTransaction = mPositionListener.getTransaction().merge(geometryTransaction);
+ }
}
mPositionListener = new SurfaceViewPositionUpdateListener(surfaceWidth, surfaceHeight,
- mSurfaceControl);
+ geometryTransaction);
mRenderNode.addPositionUpdateListener(mPositionListener);
}
private boolean performSurfaceTransaction(ViewRootImpl viewRoot, Translator translator,
boolean creating, boolean sizeChanged, boolean hintChanged,
- Transaction surfaceUpdateTransaction) {
+ Transaction geometryTransaction) {
boolean realSizeChanged = false;
mSurfaceLock.lock();
@@ -809,60 +820,59 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
// SurfaceChangedCallback to update the relative z. This is needed so that
// we do not change the relative z before the server is ready to swap the
// parent surface.
- if (creating) {
- updateRelativeZ(surfaceUpdateTransaction);
- if (mSurfacePackage != null) {
- reparentSurfacePackage(surfaceUpdateTransaction, mSurfacePackage);
- }
+ if (creating || (mParentSurfaceSequenceId == viewRoot.getSurfaceSequenceId())) {
+ updateRelativeZ(mTmpTransaction);
}
mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId();
if (mViewVisibility) {
- surfaceUpdateTransaction.show(mSurfaceControl);
+ geometryTransaction.show(mSurfaceControl);
} else {
- surfaceUpdateTransaction.hide(mSurfaceControl);
+ geometryTransaction.hide(mSurfaceControl);
}
+ if (mSurfacePackage != null) {
+ reparentSurfacePackage(mTmpTransaction, mSurfacePackage);
+ }
-
- updateBackgroundVisibility(surfaceUpdateTransaction);
- updateBackgroundColor(surfaceUpdateTransaction);
+ updateBackgroundVisibility(mTmpTransaction);
+ updateBackgroundColor(mTmpTransaction);
if (mUseAlpha) {
float alpha = getFixedAlpha();
- surfaceUpdateTransaction.setAlpha(mSurfaceControl, alpha);
+ mTmpTransaction.setAlpha(mSurfaceControl, alpha);
mSurfaceAlpha = alpha;
}
- surfaceUpdateTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
+ geometryTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
if ((sizeChanged || hintChanged) && !creating) {
- setBufferSize(surfaceUpdateTransaction);
+ setBufferSize(geometryTransaction);
}
if (sizeChanged || creating || !isHardwareAccelerated()) {
+ onSetSurfacePositionAndScaleRT(geometryTransaction, mSurfaceControl,
+ mScreenRect.left, /*positionLeft*/
+ mScreenRect.top /*positionTop*/ ,
+ mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
+ mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
// Set a window crop when creating the surface or changing its size to
// crop the buffer to the surface size since the buffer producer may
// use SCALING_MODE_SCALE and submit a larger size than the surface
// size.
if (mClipSurfaceToBounds && mClipBounds != null) {
- surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mClipBounds);
+ geometryTransaction.setWindowCrop(mSurfaceControl, mClipBounds);
} else {
- surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth,
+ geometryTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth,
mSurfaceHeight);
}
- surfaceUpdateTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth,
+ geometryTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth,
mSurfaceHeight);
if (isHardwareAccelerated()) {
// This will consume the passed in transaction and the transaction will be
// applied on a render worker thread.
- replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight);
- } else {
- onSetSurfacePositionAndScale(surfaceUpdateTransaction, mSurfaceControl,
- mScreenRect.left /*positionLeft*/,
- mScreenRect.top /*positionTop*/,
- mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
- mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
+ replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight,
+ geometryTransaction);
}
if (DEBUG_POSITION) {
Log.d(TAG, String.format(
@@ -874,7 +884,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
mScreenRect.bottom, mSurfaceWidth, mSurfaceHeight));
}
}
- applyTransactionOnVriDraw(surfaceUpdateTransaction);
+ mTmpTransaction.merge(geometryTransaction);
+ mTmpTransaction.apply();
updateEmbeddedAccessibilityMatrix();
mSurfaceFrame.left = 0;
@@ -982,17 +993,17 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
// Collect all geometry changes and apply these changes on the RenderThread worker
// via the RenderNode.PositionUpdateListener.
- final Transaction surfaceUpdateTransaction = new Transaction();
+ final Transaction geometryTransaction = new Transaction();
if (creating) {
updateOpaqueFlag();
final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
- createBlastSurfaceControls(viewRoot, name, surfaceUpdateTransaction);
+ createBlastSurfaceControls(viewRoot, name, geometryTransaction);
} else if (mSurfaceControl == null) {
return;
}
final boolean realSizeChanged = performSurfaceTransaction(viewRoot,
- translator, creating, sizeChanged, hintChanged, surfaceUpdateTransaction);
+ translator, creating, sizeChanged, hintChanged, geometryTransaction);
final boolean redrawNeeded = sizeChanged || creating || hintChanged
|| (mVisible && !mDrawFinished);
@@ -1128,7 +1139,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
*
*/
private void createBlastSurfaceControls(ViewRootImpl viewRoot, String name,
- Transaction surfaceUpdateTransaction) {
+ Transaction geometryTransaction) {
if (mSurfaceControl == null) {
mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
.setName(name)
@@ -1151,10 +1162,11 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
.build();
} else {
// update blast layer
- surfaceUpdateTransaction
+ mTmpTransaction
.setOpaque(mBlastSurfaceControl, (mSurfaceFlags & SurfaceControl.OPAQUE) != 0)
.setSecure(mBlastSurfaceControl, (mSurfaceFlags & SurfaceControl.SECURE) != 0)
- .show(mBlastSurfaceControl);
+ .show(mBlastSurfaceControl)
+ .apply();
}
if (mBackgroundControl == null) {
@@ -1201,7 +1213,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
*
* @hide
*/
- protected void onSetSurfacePositionAndScale(@NonNull Transaction transaction,
+ protected void onSetSurfacePositionAndScaleRT(@NonNull Transaction transaction,
@NonNull SurfaceControl surface, int positionLeft, int positionTop,
float postScaleX, float postScaleY) {
transaction.setPosition(surface, positionLeft, positionTop);
@@ -1214,14 +1226,12 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
if (mSurfaceControl == null) {
return;
}
- final Transaction transaction = new Transaction();
- onSetSurfacePositionAndScale(transaction, mSurfaceControl,
+ onSetSurfacePositionAndScaleRT(mTmpTransaction, mSurfaceControl,
mScreenRect.left, /*positionLeft*/
mScreenRect.top/*positionTop*/ ,
mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
- applyTransactionOnVriDraw(transaction);
- invalidate();
+ mTmpTransaction.apply();
}
/**
@@ -1243,57 +1253,66 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
}
}
- private final Rect mRTLastReportedPosition = new Rect();
- private final Point mRTLastReportedSurfaceSize = new Point();
+ private Rect mRTLastReportedPosition = new Rect();
+ private Point mRTLastReportedSurfaceSize = new Point();
private class SurfaceViewPositionUpdateListener implements RenderNode.PositionUpdateListener {
- private final int mRtSurfaceWidth;
- private final int mRtSurfaceHeight;
+ int mRtSurfaceWidth = -1;
+ int mRtSurfaceHeight = -1;
private final SurfaceControl.Transaction mPositionChangedTransaction =
new SurfaceControl.Transaction();
- private final SurfaceControl mRtSurfaceControl = new SurfaceControl();
+ boolean mPendingTransaction = false;
SurfaceViewPositionUpdateListener(int surfaceWidth, int surfaceHeight,
- SurfaceControl surfaceControl) {
+ @Nullable Transaction t) {
mRtSurfaceWidth = surfaceWidth;
mRtSurfaceHeight = surfaceHeight;
- mRtSurfaceControl.copyFrom(surfaceControl, "SurfaceViewPositionUpdateListener");
+ if (t != null) {
+ mPositionChangedTransaction.merge(t);
+ mPendingTransaction = true;
+ }
}
@Override
public void positionChanged(long frameNumber, int left, int top, int right, int bottom) {
- if (mRTLastReportedPosition.left == left
- && mRTLastReportedPosition.top == top
- && mRTLastReportedPosition.right == right
- && mRTLastReportedPosition.bottom == bottom
- && mRTLastReportedSurfaceSize.x == mRtSurfaceWidth
- && mRTLastReportedSurfaceSize.y == mRtSurfaceHeight) {
- return;
- }
- try {
- if (DEBUG_POSITION) {
- Log.d(TAG, String.format(
- "%d updateSurfacePosition RenderWorker, frameNr = %d, "
- + "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
- System.identityHashCode(SurfaceView.this), frameNumber,
- left, top, right, bottom, mRtSurfaceWidth, mRtSurfaceHeight));
+ synchronized(mSurfaceControlLock) {
+ if (mSurfaceControl == null) {
+ return;
}
- mRTLastReportedPosition.set(left, top, right, bottom);
- mRTLastReportedSurfaceSize.set(mRtSurfaceWidth, mRtSurfaceHeight);
- onSetSurfacePositionAndScale(mPositionChangedTransaction, mRtSurfaceControl,
- mRTLastReportedPosition.left /*positionLeft*/,
- mRTLastReportedPosition.top /*positionTop*/,
- mRTLastReportedPosition.width()
- / (float) mRtSurfaceWidth /*postScaleX*/,
- mRTLastReportedPosition.height()
- / (float) mRtSurfaceHeight /*postScaleY*/);
- if (mViewVisibility) {
- // b/131239825
- mPositionChangedTransaction.show(mRtSurfaceControl);
+ if (mRTLastReportedPosition.left == left
+ && mRTLastReportedPosition.top == top
+ && mRTLastReportedPosition.right == right
+ && mRTLastReportedPosition.bottom == bottom
+ && mRTLastReportedSurfaceSize.x == mRtSurfaceWidth
+ && mRTLastReportedSurfaceSize.y == mRtSurfaceHeight
+ && !mPendingTransaction) {
+ return;
+ }
+ try {
+ if (DEBUG_POSITION) {
+ Log.d(TAG, String.format(
+ "%d updateSurfacePosition RenderWorker, frameNr = %d, "
+ + "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
+ System.identityHashCode(SurfaceView.this), frameNumber,
+ left, top, right, bottom, mRtSurfaceWidth, mRtSurfaceHeight));
+ }
+ mRTLastReportedPosition.set(left, top, right, bottom);
+ mRTLastReportedSurfaceSize.set(mRtSurfaceWidth, mRtSurfaceHeight);
+ onSetSurfacePositionAndScaleRT(mPositionChangedTransaction, mSurfaceControl,
+ mRTLastReportedPosition.left /*positionLeft*/,
+ mRTLastReportedPosition.top /*positionTop*/,
+ mRTLastReportedPosition.width()
+ / (float) mRtSurfaceWidth /*postScaleX*/,
+ mRTLastReportedPosition.height()
+ / (float) mRtSurfaceHeight /*postScaleY*/);
+ if (mViewVisibility) {
+ mPositionChangedTransaction.show(mSurfaceControl);
+ }
+ applyOrMergeTransaction(mPositionChangedTransaction, frameNumber);
+ mPendingTransaction = false;
+ } catch (Exception ex) {
+ Log.e(TAG, "Exception from repositionChild", ex);
}
- applyOrMergeTransaction(mPositionChangedTransaction, frameNumber);
- } catch (Exception ex) {
- Log.e(TAG, "Exception from repositionChild", ex);
}
}
@@ -1302,7 +1321,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
float vecX, float vecY, float maxStretchX, float maxStretchY,
float childRelativeLeft, float childRelativeTop, float childRelativeRight,
float childRelativeBottom) {
- mRtTransaction.setStretchEffect(mRtSurfaceControl, width, height, vecX, vecY,
+ mRtTransaction.setStretchEffect(mSurfaceControl, width, height, vecX, vecY,
maxStretchX, maxStretchY, childRelativeLeft, childRelativeTop,
childRelativeRight, childRelativeBottom);
applyOrMergeTransaction(mRtTransaction, frameNumber);
@@ -1317,14 +1336,28 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
mRTLastReportedPosition.setEmpty();
mRTLastReportedSurfaceSize.set(-1, -1);
- // positionLost can be called while UI thread is un-paused.
+ /**
+ * positionLost can be called while UI thread is un-paused so we
+ * need to hold the lock here.
+ */
synchronized (mSurfaceControlLock) {
- if (mSurfaceControl == null) return;
- // b/131239825
+ if (mPendingTransaction) {
+ Log.w(TAG, System.identityHashCode(SurfaceView.this)
+ + "Pending transaction cleared.");
+ mPositionChangedTransaction.clear();
+ mPendingTransaction = false;
+ }
+ if (mSurfaceControl == null) {
+ return;
+ }
mRtTransaction.hide(mSurfaceControl);
applyOrMergeTransaction(mRtTransaction, frameNumber);
}
}
+
+ public Transaction getTransaction() {
+ return mPositionChangedTransaction;
+ }
}
private SurfaceViewPositionUpdateListener mPositionListener = null;
@@ -1371,10 +1404,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
* @hide
*/
public void setResizeBackgroundColor(int bgColor) {
- final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
- setResizeBackgroundColor(transaction, bgColor);
- applyTransactionOnVriDraw(transaction);
- invalidate();
+ setResizeBackgroundColor(mTmpTransaction, bgColor);
+ mTmpTransaction.apply();
}
/**
diff --git a/core/java/android/view/SyncRtSurfaceTransactionApplier.java b/core/java/android/view/SyncRtSurfaceTransactionApplier.java
index 3e2110341693..e9c937cc0f9b 100644
--- a/core/java/android/view/SyncRtSurfaceTransactionApplier.java
+++ b/core/java/android/view/SyncRtSurfaceTransactionApplier.java
@@ -65,10 +65,12 @@ public class SyncRtSurfaceTransactionApplier {
applyParams(t, params);
mTargetViewRootImpl.registerRtFrameCallback(frame -> {
- if (mTargetSc == null || !mTargetSc.isValid()) {
- return;
+ if (mTargetSc != null && mTargetSc.isValid()) {
+ applyTransaction(t, frame);
}
- applyTransaction(t, frame);
+ // The transaction was either dropped, successfully applied, or merged with a future
+ // transaction, so we can safely release its resources.
+ t.close();
});
// Make sure a frame gets scheduled.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 553c537d9b95..8b3a29a7aa6f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -46,9 +46,11 @@ import android.annotation.IntRange;
import android.annotation.LayoutRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.Size;
import android.annotation.StyleRes;
import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UiContext;
import android.annotation.UiThread;
@@ -4744,6 +4746,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
private List<Rect> mSystemGestureExclusionRects = null;
private List<Rect> mKeepClearRects = null;
+ private List<Rect> mUnrestrictedKeepClearRects = null;
private boolean mPreferKeepClear = false;
private Rect mHandwritingArea = null;
@@ -11729,6 +11732,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
final ListenerInfo info = getListenerInfo();
if (getSystemGestureExclusionRects().isEmpty()
&& collectPreferKeepClearRects().isEmpty()
+ && collectUnrestrictedPreferKeepClearRects().isEmpty()
&& (info.mHandwritingArea == null || !isAutoHandwritingEnabled())) {
if (info.mPositionUpdateListener != null) {
mRenderNode.removePositionUpdateListener(info.mPositionUpdateListener);
@@ -11871,6 +11875,52 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return Collections.emptyList();
}
+ /**
+ * Set a preference to keep the provided rects clear from floating windows above this
+ * view's window. This informs the system that these rects are considered vital areas for the
+ * user and that ideally they should not be covered. Setting this is only appropriate for UI
+ * where the user would likely take action to uncover it.
+ * <p>
+ * Note: The difference with {@link #setPreferKeepClearRects} is that the system won't apply
+ * restrictions to the rects set here.
+ * <p>
+ * @see #setPreferKeepClear
+ * @see #getPreferKeepClearRects
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS)
+ public final void setUnrestrictedPreferKeepClearRects(@NonNull List<Rect> rects) {
+ final ListenerInfo info = getListenerInfo();
+ if (info.mUnrestrictedKeepClearRects != null) {
+ info.mUnrestrictedKeepClearRects.clear();
+ info.mUnrestrictedKeepClearRects.addAll(rects);
+ } else {
+ info.mUnrestrictedKeepClearRects = new ArrayList<>(rects);
+ }
+ updatePositionUpdateListener();
+ postUpdate(this::updateKeepClearRects);
+ }
+
+ /**
+ * @return the list of rects, set by {@link #setPreferKeepClearRects}.
+ *
+ * @see #setPreferKeepClearRects
+ *
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public final List<Rect> getUnrestrictedPreferKeepClearRects() {
+ final ListenerInfo info = mListenerInfo;
+ if (info != null && info.mKeepClearRects != null) {
+ return new ArrayList(info.mUnrestrictedKeepClearRects);
+ }
+
+ return Collections.emptyList();
+ }
+
void updateKeepClearRects() {
final AttachInfo ai = mAttachInfo;
if (ai != null) {
@@ -11899,6 +11949,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Retrieve the list of unrestricted areas within this view's post-layout coordinate space
+ * which the system will try to not cover with other floating elements, like the pip window.
+ */
+ @NonNull
+ List<Rect> collectUnrestrictedPreferKeepClearRects() {
+ final ListenerInfo info = mListenerInfo;
+ if (info != null && info.mUnrestrictedKeepClearRects != null) {
+ return info.mUnrestrictedKeepClearRects;
+ }
+
+ return Collections.emptyList();
+ }
+
+ /**
* Set a list of handwriting areas in this view. If there is any stylus {@link MotionEvent}
* occurs within those areas, it will trigger stylus handwriting mode. This can be disabled by
* disabling the auto handwriting initiation by calling
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a9a2689bd318..d3d36255d05a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -221,6 +221,7 @@ import java.io.StringWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
@@ -778,6 +779,8 @@ public final class ViewRootImpl implements ViewParent,
new ViewRootRectTracker(v -> v.getSystemGestureExclusionRects());
private final ViewRootRectTracker mKeepClearRectsTracker =
new ViewRootRectTracker(v -> v.collectPreferKeepClearRects());
+ private final ViewRootRectTracker mUnrestrictedKeepClearRectsTracker =
+ new ViewRootRectTracker(v -> v.collectUnrestrictedPreferKeepClearRects());
private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
@@ -4178,7 +4181,7 @@ public final class ViewRootImpl implements ViewParent,
+ " didProduceBuffer=" + didProduceBuffer);
}
- Transaction tmpTransaction = new Transaction();
+ final Transaction tmpTransaction = new Transaction();
tmpTransaction.merge(mRtBLASTSyncTransaction);
// If frame wasn't drawn, clear out the next transaction so it doesn't affect the next
@@ -4209,6 +4212,7 @@ public final class ViewRootImpl implements ViewParent,
blastSyncConsumer.accept(mSurfaceChangedTransaction);
}
}
+ tmpTransaction.close();
if (reportNextDraw) {
pendingDrawFinished();
@@ -4878,14 +4882,26 @@ public final class ViewRootImpl implements ViewParent,
*/
void updateKeepClearRectsForView(View view) {
mKeepClearRectsTracker.updateRectsForView(view);
+ mUnrestrictedKeepClearRectsTracker.updateRectsForView(view);
mHandler.sendEmptyMessage(MSG_KEEP_CLEAR_RECTS_CHANGED);
}
void keepClearRectsChanged() {
- final List<Rect> rectsForWindowManager = mKeepClearRectsTracker.computeChangedRects();
- if (rectsForWindowManager != null && mView != null) {
+ List<Rect> restrictedKeepClearRects = mKeepClearRectsTracker.computeChangedRects();
+ List<Rect> unrestrictedKeepClearRects =
+ mUnrestrictedKeepClearRectsTracker.computeChangedRects();
+ if ((restrictedKeepClearRects != null || unrestrictedKeepClearRects != null)
+ && mView != null) {
+ if (restrictedKeepClearRects == null) {
+ restrictedKeepClearRects = Collections.emptyList();
+ }
+ if (unrestrictedKeepClearRects == null) {
+ unrestrictedKeepClearRects = Collections.emptyList();
+ }
+
try {
- mWindowSession.reportKeepClearAreasChanged(mWindow, rectsForWindowManager);
+ mWindowSession.reportKeepClearAreasChanged(mWindow, restrictedKeepClearRects,
+ unrestrictedKeepClearRects);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index c81b8ccf94d6..a270c9283ddc 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -31,6 +31,7 @@ import android.window.ClientWindowFrames;
import android.window.IOnBackInvokedCallback;
import java.util.HashMap;
+import java.util.List;
import java.util.Objects;
/**
@@ -473,12 +474,12 @@ public class WindowlessWindowManager implements IWindowSession {
@Override
public void reportSystemGestureExclusionChanged(android.view.IWindow window,
- java.util.List<android.graphics.Rect> exclusionRects) {
+ List<Rect> exclusionRects) {
}
@Override
- public void reportKeepClearAreasChanged(android.view.IWindow window,
- java.util.List<android.graphics.Rect> exclusionRects) {
+ public void reportKeepClearAreasChanged(android.view.IWindow window, List<Rect> restrictedRects,
+ List<Rect> unrestrictedRects) {
}
@Override
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
index 67d96678f420..62d029bd1be6 100644
--- a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
+++ b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
@@ -32,7 +32,7 @@ oneway interface IWindowMagnificationConnection {
/**
* Enables window magnification on specified display with given center and scale and animation.
*
- * @param displayId The logical display id.
+ * @param displayId the logical display id.
* @param scale magnification scale.
* @param centerX the screen-relative X coordinate around which to center,
* or {@link Float#NaN} to leave unchanged.
@@ -51,7 +51,7 @@ oneway interface IWindowMagnificationConnection {
/**
* Sets the scale of the window magnifier on specified display.
*
- * @param displayId The logical display id.
+ * @param displayId the logical display id.
* @param scale magnification scale.
*/
void setScale(int displayId, float scale);
@@ -59,7 +59,7 @@ oneway interface IWindowMagnificationConnection {
/**
* Disables window magnification on specified display with animation.
*
- * @param displayId The logical display id.
+ * @param displayId the logical display id.
* @param callback The callback called when the animation is completed or interrupted.
*/
void disableWindowMagnification(int displayId,
@@ -68,6 +68,7 @@ oneway interface IWindowMagnificationConnection {
/**
* Moves the window magnifier on the specified display. It has no effect while animating.
*
+ * @param displayId the logical display id.
* @param offsetX the amount in pixels to offset the window magnifier in the X direction, in
* current screen pixels.
* @param offsetY the amount in pixels to offset the window magnifier in the Y direction, in
@@ -76,9 +77,20 @@ oneway interface IWindowMagnificationConnection {
void moveWindowMagnifier(int displayId, float offsetX, float offsetY);
/**
+ * Moves the window magnifier on the given display.
+ *
+ * @param displayId the logical display id.
+ * @param positionX the x-axis position of the center of the magnified source bounds.
+ * @param positionY the y-axis position of the center of the magnified source bounds.
+ * @param callback the callback called when the animation is completed or interrupted.
+ */
+ void moveWindowMagnifierToPosition(int displayId, float positionX, float positionY,
+ in IRemoteMagnificationAnimationCallback callback);
+
+ /**
* Requests System UI show magnification mode button UI on the specified display.
*
- * @param displayId The logical display id.
+ * @param displayId the logical display id.
* @param magnificationMode the current magnification mode.
*/
void showMagnificationButton(int displayId, int magnificationMode);
@@ -86,7 +98,7 @@ oneway interface IWindowMagnificationConnection {
/**
* Requests System UI remove magnification mode button UI on the specified display.
*
- * @param displayId The logical display id.
+ * @param displayId the logical display id.
*/
void removeMagnificationButton(int displayId);
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl
index 722546eb06e4..adfeb6d11008 100644
--- a/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl
@@ -68,12 +68,10 @@ import android.graphics.Rect;
void onAccessibilityActionPerformed(int displayId);
/**
- * Called when the user is performing dragging gesture. It is started after the offset
- * between the down location and the move event location exceed
- * {@link ViewConfiguration#getScaledTouchSlop()}.
+ * Called when the user is performing move action.
*
* @param displayId The logical display id.
*/
- void onDrag(int displayId);
+ void onMove(int displayId);
}
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index 2bfbe4bdfba7..bc7a5fda6f7a 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -25,6 +25,7 @@ import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.Zygote;
/** @hide */
public class WebViewZygote {
@@ -127,13 +128,15 @@ public class WebViewZygote {
try {
String abi = sPackage.applicationInfo.primaryCpuAbi;
+ int runtimeFlags = Zygote.getMemorySafetyRuntimeFlagsForSecondaryZygote(
+ sPackage.applicationInfo, null);
sZygote = Process.ZYGOTE_PROCESS.startChildZygote(
"com.android.internal.os.WebViewZygoteInit",
"webview_zygote",
Process.WEBVIEW_ZYGOTE_UID,
Process.WEBVIEW_ZYGOTE_UID,
null, // gids
- 0, // runtimeFlags
+ runtimeFlags,
"webview_zygote", // seInfo
abi, // abi
TextUtils.join(",", Build.SUPPORTED_ABIS),
diff --git a/core/java/android/widget/inline/InlineContentView.java b/core/java/android/widget/inline/InlineContentView.java
index e4f483a29343..9712311aab7c 100644
--- a/core/java/android/widget/inline/InlineContentView.java
+++ b/core/java/android/widget/inline/InlineContentView.java
@@ -230,9 +230,8 @@ public class InlineContentView extends ViewGroup {
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mSurfaceView = new SurfaceView(context, attrs, defStyleAttr, defStyleRes) {
- // b/219807628
@Override
- protected void onSetSurfacePositionAndScale(
+ protected void onSetSurfacePositionAndScaleRT(
@NonNull SurfaceControl.Transaction transaction,
@NonNull SurfaceControl surface, int positionLeft, int positionTop,
float postScaleX, float postScaleY) {
@@ -249,7 +248,7 @@ public class InlineContentView extends ViewGroup {
postScaleX = InlineContentView.this.getScaleX();
postScaleY = InlineContentView.this.getScaleY();
- super.onSetSurfacePositionAndScale(transaction, surface, positionLeft,
+ super.onSetSurfacePositionAndScaleRT(transaction, surface, positionLeft,
positionTop, postScaleX, postScaleY);
}
};
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index 34a34180b227..232248b6fab3 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -65,6 +65,7 @@ import com.android.internal.util.ContrastColorUtil;
import java.time.Duration;
import java.time.Instant;
import java.util.function.Consumer;
+import java.util.function.LongConsumer;
/**
* <p>The view which allows an activity to customize its splash screen exit animation.</p>
@@ -234,7 +235,7 @@ public final class SplashScreenView extends FrameLayout {
/**
* Set the animation duration if icon is animatable.
*/
- public Builder setAnimationDurationMillis(int duration) {
+ public Builder setAnimationDurationMillis(long duration) {
mIconAnimationDuration = Duration.ofMillis(duration);
return this;
}
@@ -521,8 +522,11 @@ public final class SplashScreenView extends FrameLayout {
});
}
- private void animationStartCallback() {
+ private void animationStartCallback(long animDuration) {
mIconAnimationStart = Instant.now();
+ if (animDuration > 0) {
+ mIconAnimationDuration = Duration.ofMillis(animDuration);
+ }
}
/**
@@ -693,9 +697,8 @@ public final class SplashScreenView extends FrameLayout {
* Prepare the animation if this drawable also be animatable.
* @param duration The animation duration.
* @param startListener The callback listener used to receive the start of the animation.
- * @return true if this drawable object can also be animated and it can be played now.
*/
- boolean prepareAnimate(long duration, Runnable startListener);
+ void prepareAnimate(long duration, LongConsumer startListener);
/**
* Stop animation.
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 94e5ea960f4f..909e2770ddcc 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -54,7 +54,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
private static final String TAG = "WindowOnBackDispatcher";
private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability";
private static final boolean IS_BACK_PREDICTABILITY_ENABLED = SystemProperties
- .getInt(BACK_PREDICTABILITY_PROP, 1) > 0;
+ .getInt(BACK_PREDICTABILITY_PROP, 0) > 0;
/** Convenience hashmap to quickly decide if a callback has been added. */
private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>();
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 4ae6bf7e8379..70506ccaebb8 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -771,11 +771,6 @@ public class ChooserActivity extends ResolverActivity implements
delegationIntent.setComponent(delegateActivity);
delegationIntent.putExtra(Intent.EXTRA_INTENT, getIntent());
delegationIntent.putExtra(ActivityTaskManager.EXTRA_PERMISSION_TOKEN, permissionToken);
-
- // Query prediction availability; mIsAppPredictorComponentAvailable isn't initialized.
- delegationIntent.putExtra(
- EXTRA_IS_APP_PREDICTION_SERVICE_AVAILABLE, isAppPredictionServiceAvailable());
-
delegationIntent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
// Don't close until the delegate finishes, or the token will be invalidated.
@@ -972,34 +967,8 @@ public class ChooserActivity extends ResolverActivity implements
/**
* Returns true if app prediction service is defined and the component exists on device.
*/
- @VisibleForTesting
- public boolean isAppPredictionServiceAvailable() {
- if (getPackageManager().getAppPredictionServicePackageName() == null) {
- // Default AppPredictionService is not defined.
- return false;
- }
-
- final String appPredictionServiceName =
- getString(R.string.config_defaultAppPredictionService);
- if (appPredictionServiceName == null) {
- return false;
- }
- final ComponentName appPredictionComponentName =
- ComponentName.unflattenFromString(appPredictionServiceName);
- if (appPredictionComponentName == null) {
- return false;
- }
-
- // Check if the app prediction component actually exists on the device. The component is
- // only visible when this is running in a system activity; otherwise this check will fail.
- Intent intent = new Intent();
- intent.setComponent(appPredictionComponentName);
- if (getPackageManager().resolveService(intent, PackageManager.MATCH_ALL) == null) {
- Log.e(TAG, "App prediction service is defined, but does not exist: "
- + appPredictionServiceName);
- return false;
- }
- return true;
+ private boolean isAppPredictionServiceAvailable() {
+ return getPackageManager().getAppPredictionServicePackageName() != null;
}
/**
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 6d4b8c5ea1ad..b1e7d15cbf4a 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -20,13 +20,21 @@ import static android.system.OsConstants.O_CLOEXEC;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.compat.annotation.EnabledAfter;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.ProcessInfo;
import android.net.Credentials;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
+import android.os.Build;
import android.os.FactoryTest;
import android.os.IVold;
import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.Trace;
import android.provider.DeviceConfig;
@@ -34,6 +42,7 @@ import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
+import com.android.internal.compat.IPlatformCompat;
import com.android.internal.net.NetworkUtilsInternal;
import dalvik.annotation.optimization.CriticalNative;
@@ -125,6 +134,7 @@ public final class Zygote {
public static final int MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20);
public static final int MEMORY_TAG_LEVEL_NONE = 0;
+
/**
* Enable pointer tagging in this process.
* Tags are checked during memory deallocation, but not on access.
@@ -170,10 +180,8 @@ public final class Zygote {
*/
public static final int GWP_ASAN_LEVEL_ALWAYS = 1 << 22;
- /**
- * Enable automatic zero-initialization of native heap memory allocations.
- */
- public static final int NATIVE_HEAP_ZERO_INIT = 1 << 23;
+ /** Enable automatic zero-initialization of native heap memory allocations. */
+ public static final int NATIVE_HEAP_ZERO_INIT_ENABLED = 1 << 23;
/**
* Enable profiling from system services. This loads profiling related plugins in ART.
@@ -1170,4 +1178,251 @@ public final class Zygote {
* we failed to determine the level.
*/
public static native int nativeCurrentTaggingLevel();
+
+ /**
+ * Native heap allocations will now have a non-zero tag in the most significant byte.
+ *
+ * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged
+ * Pointers</a>
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id.
+
+ /**
+ * Native heap allocations in AppZygote process and its descendants will now have a non-zero tag
+ * in the most significant byte.
+ *
+ * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged
+ * Pointers</a>
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S)
+ private static final long NATIVE_HEAP_POINTER_TAGGING_SECONDARY_ZYGOTE = 207557677;
+
+ /**
+ * Enable asynchronous (ASYNC) memory tag checking in this process. This flag will only have an
+ * effect on hardware supporting the ARM Memory Tagging Extension (MTE).
+ */
+ @ChangeId @Disabled
+ private static final long NATIVE_MEMTAG_ASYNC = 135772972; // This is a bug id.
+
+ /**
+ * Enable synchronous (SYNC) memory tag checking in this process. This flag will only have an
+ * effect on hardware supporting the ARM Memory Tagging Extension (MTE). If both
+ * NATIVE_MEMTAG_ASYNC and this option is selected, this option takes preference and MTE is
+ * enabled in SYNC mode.
+ */
+ @ChangeId @Disabled
+ private static final long NATIVE_MEMTAG_SYNC = 177438394; // This is a bug id.
+
+ /** Enable automatic zero-initialization of native heap memory allocations. */
+ @ChangeId @Disabled
+ private static final long NATIVE_HEAP_ZERO_INIT = 178038272; // This is a bug id.
+
+ /**
+ * Enable sampled memory bug detection in the app.
+ *
+ * @see <a href="https://source.android.com/devices/tech/debug/gwp-asan">GWP-ASan</a>.
+ */
+ @ChangeId @Disabled private static final long GWP_ASAN = 135634846; // This is a bug id.
+
+ private static int memtagModeToZygoteMemtagLevel(int memtagMode) {
+ switch (memtagMode) {
+ case ApplicationInfo.MEMTAG_ASYNC:
+ return MEMORY_TAG_LEVEL_ASYNC;
+ case ApplicationInfo.MEMTAG_SYNC:
+ return MEMORY_TAG_LEVEL_SYNC;
+ default:
+ return MEMORY_TAG_LEVEL_NONE;
+ }
+ }
+
+ private static boolean isCompatChangeEnabled(
+ long change,
+ @NonNull ApplicationInfo info,
+ @Nullable IPlatformCompat platformCompat,
+ int enabledAfter) {
+ try {
+ if (platformCompat != null) return platformCompat.isChangeEnabled(change, info);
+ } catch (RemoteException ignore) {
+ }
+ return enabledAfter > 0 && info.targetSdkVersion > enabledAfter;
+ }
+
+ // Returns the requested memory tagging level.
+ private static int getRequestedMemtagLevel(
+ @NonNull ApplicationInfo info,
+ @Nullable ProcessInfo processInfo,
+ @Nullable IPlatformCompat platformCompat) {
+ // Look at the process attribute first.
+ if (processInfo != null && processInfo.memtagMode != ApplicationInfo.MEMTAG_DEFAULT) {
+ return memtagModeToZygoteMemtagLevel(processInfo.memtagMode);
+ }
+
+ // Then at the application attribute.
+ if (info.getMemtagMode() != ApplicationInfo.MEMTAG_DEFAULT) {
+ return memtagModeToZygoteMemtagLevel(info.getMemtagMode());
+ }
+
+ if (isCompatChangeEnabled(NATIVE_MEMTAG_SYNC, info, platformCompat, 0)) {
+ return MEMORY_TAG_LEVEL_SYNC;
+ }
+
+ if (isCompatChangeEnabled(NATIVE_MEMTAG_ASYNC, info, platformCompat, 0)) {
+ return MEMORY_TAG_LEVEL_ASYNC;
+ }
+
+ // Check to ensure the app hasn't explicitly opted-out of TBI via. the manifest attribute.
+ if (!info.allowsNativeHeapPointerTagging()) {
+ return MEMORY_TAG_LEVEL_NONE;
+ }
+
+ String defaultLevel = SystemProperties.get("persist.arm64.memtag.app_default");
+ if ("sync".equals(defaultLevel)) {
+ return MEMORY_TAG_LEVEL_SYNC;
+ } else if ("async".equals(defaultLevel)) {
+ return MEMORY_TAG_LEVEL_ASYNC;
+ }
+
+ // Check to see that the compat feature for TBI is enabled.
+ if (isCompatChangeEnabled(
+ NATIVE_HEAP_POINTER_TAGGING, info, platformCompat, Build.VERSION_CODES.Q)) {
+ return MEMORY_TAG_LEVEL_TBI;
+ }
+
+ return MEMORY_TAG_LEVEL_NONE;
+ }
+
+ private static int decideTaggingLevel(
+ @NonNull ApplicationInfo info,
+ @Nullable ProcessInfo processInfo,
+ @Nullable IPlatformCompat platformCompat) {
+ // Get the desired tagging level (app manifest + compat features).
+ int level = getRequestedMemtagLevel(info, processInfo, platformCompat);
+
+ // Take into account the hardware capabilities.
+ if (nativeSupportsMemoryTagging()) {
+ // MTE devices can not do TBI, because the Zygote process already has live MTE
+ // allocations. Downgrade TBI to NONE.
+ if (level == MEMORY_TAG_LEVEL_TBI) {
+ level = MEMORY_TAG_LEVEL_NONE;
+ }
+ } else if (nativeSupportsTaggedPointers()) {
+ // TBI-but-not-MTE devices downgrade MTE modes to TBI.
+ // The idea is that if an app opts into full hardware tagging (MTE), it must be ok with
+ // the "fake" pointer tagging (TBI).
+ if (level == MEMORY_TAG_LEVEL_ASYNC || level == MEMORY_TAG_LEVEL_SYNC) {
+ level = MEMORY_TAG_LEVEL_TBI;
+ }
+ } else {
+ // Otherwise disable all tagging.
+ level = MEMORY_TAG_LEVEL_NONE;
+ }
+
+ return level;
+ }
+
+ private static int decideGwpAsanLevel(
+ @NonNull ApplicationInfo info,
+ @Nullable ProcessInfo processInfo,
+ @Nullable IPlatformCompat platformCompat) {
+ // Look at the process attribute first.
+ if (processInfo != null && processInfo.gwpAsanMode != ApplicationInfo.GWP_ASAN_DEFAULT) {
+ return processInfo.gwpAsanMode == ApplicationInfo.GWP_ASAN_ALWAYS
+ ? GWP_ASAN_LEVEL_ALWAYS
+ : GWP_ASAN_LEVEL_NEVER;
+ }
+ // Then at the application attribute.
+ if (info.getGwpAsanMode() != ApplicationInfo.GWP_ASAN_DEFAULT) {
+ return info.getGwpAsanMode() == ApplicationInfo.GWP_ASAN_ALWAYS
+ ? GWP_ASAN_LEVEL_ALWAYS
+ : GWP_ASAN_LEVEL_NEVER;
+ }
+ // If the app does not specify gwpAsanMode, the default behavior is lottery among the
+ // system apps, and disabled for user apps, unless overwritten by the compat feature.
+ if (isCompatChangeEnabled(GWP_ASAN, info, platformCompat, 0)) {
+ return GWP_ASAN_LEVEL_ALWAYS;
+ }
+ if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ return GWP_ASAN_LEVEL_LOTTERY;
+ }
+ return GWP_ASAN_LEVEL_NEVER;
+ }
+
+ private static boolean enableNativeHeapZeroInit(
+ @NonNull ApplicationInfo info,
+ @Nullable ProcessInfo processInfo,
+ @Nullable IPlatformCompat platformCompat) {
+ // Look at the process attribute first.
+ if (processInfo != null
+ && processInfo.nativeHeapZeroInitialized != ApplicationInfo.ZEROINIT_DEFAULT) {
+ return processInfo.nativeHeapZeroInitialized == ApplicationInfo.ZEROINIT_ENABLED;
+ }
+ // Then at the application attribute.
+ if (info.getNativeHeapZeroInitialized() != ApplicationInfo.ZEROINIT_DEFAULT) {
+ return info.getNativeHeapZeroInitialized() == ApplicationInfo.ZEROINIT_ENABLED;
+ }
+ // Compat feature last.
+ if (isCompatChangeEnabled(NATIVE_HEAP_ZERO_INIT, info, platformCompat, 0)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns Zygote runtimeFlags for memory safety features (MTE, GWP-ASan, nativeHeadZeroInit)
+ * for a given app.
+ */
+ public static int getMemorySafetyRuntimeFlags(
+ @NonNull ApplicationInfo info,
+ @Nullable ProcessInfo processInfo,
+ @Nullable String instructionSet,
+ @Nullable IPlatformCompat platformCompat) {
+ int runtimeFlags = decideGwpAsanLevel(info, processInfo, platformCompat);
+ // If instructionSet is non-null, this indicates that the system_server is spawning a
+ // process with an ISA that may be different from its own. System (kernel and hardware)
+ // compatibility for these features is checked in the decideTaggingLevel in the
+ // system_server process (not the child process). As both MTE and TBI are only supported
+ // in aarch64, we can simply ensure that the new process is also aarch64. This prevents
+ // the mismatch where a 64-bit system server spawns a 32-bit child that thinks it should
+ // enable some tagging variant. Theoretically, a 32-bit system server could exist that
+ // spawns 64-bit processes, in which case the new process won't get any tagging. This is
+ // fine as we haven't seen this configuration in practice, and we can reasonable assume
+ // that if tagging is desired, the system server will be 64-bit.
+ if (instructionSet == null || instructionSet.equals("arm64")) {
+ runtimeFlags |= decideTaggingLevel(info, processInfo, platformCompat);
+ }
+ if (enableNativeHeapZeroInit(info, processInfo, platformCompat)) {
+ runtimeFlags |= NATIVE_HEAP_ZERO_INIT_ENABLED;
+ }
+ return runtimeFlags;
+ }
+
+ /**
+ * Returns Zygote runtimeFlags for memory safety features (MTE, GWP-ASan, nativeHeadZeroInit)
+ * for a secondary zygote (AppZygote or WebViewZygote).
+ */
+ public static int getMemorySafetyRuntimeFlagsForSecondaryZygote(
+ @NonNull ApplicationInfo info, @Nullable ProcessInfo processInfo) {
+ final IPlatformCompat platformCompat =
+ IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+ int runtimeFlags =
+ getMemorySafetyRuntimeFlags(
+ info, processInfo, null /*instructionSet*/, platformCompat);
+
+ // TBI ("fake" pointer tagging) in AppZygote is controlled by a separate compat feature.
+ if ((runtimeFlags & MEMORY_TAG_LEVEL_MASK) == MEMORY_TAG_LEVEL_TBI
+ && isCompatChangeEnabled(
+ NATIVE_HEAP_POINTER_TAGGING_SECONDARY_ZYGOTE,
+ info,
+ platformCompat,
+ Build.VERSION_CODES.S)) {
+ // Reset memory tag level to NONE.
+ runtimeFlags &= ~MEMORY_TAG_LEVEL_MASK;
+ runtimeFlags |= MEMORY_TAG_LEVEL_NONE;
+ }
+ return runtimeFlags;
+ }
}
diff --git a/core/java/com/android/internal/policy/ForceShowNavigationBarSettingsObserver.java b/core/java/com/android/internal/policy/ForceShowNavigationBarSettingsObserver.java
index 3e72564bad5d..75dce5a278ff 100644
--- a/core/java/com/android/internal/policy/ForceShowNavigationBarSettingsObserver.java
+++ b/core/java/com/android/internal/policy/ForceShowNavigationBarSettingsObserver.java
@@ -26,7 +26,7 @@ import android.provider.Settings;
/**
* A ContentObserver for listening force show navigation bar relative setting keys:
* - {@link Settings.Secure#NAVIGATION_MODE}
- * - {@link Settings.Secure#NAV_BAR_KIDS_MODE}
+ * - {@link Settings.Secure#NAV_BAR_FORCE_VISIBLE}
*
* @hide
*/
@@ -52,7 +52,7 @@ public class ForceShowNavigationBarSettingsObserver extends ContentObserver {
Settings.Secure.getUriFor(Settings.Secure.NAVIGATION_MODE),
false, this, UserHandle.USER_ALL);
r.registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE),
+ Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_FORCE_VISIBLE),
false, this, UserHandle.USER_ALL);
}
@@ -78,6 +78,6 @@ public class ForceShowNavigationBarSettingsObserver extends ContentObserver {
return Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.NAVIGATION_MODE, 0, UserHandle.USER_CURRENT) == 0
&& Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.NAV_BAR_KIDS_MODE, 0, UserHandle.USER_CURRENT) == 1;
+ Settings.Secure.NAV_BAR_FORCE_VISIBLE, 0, UserHandle.USER_CURRENT) == 1;
}
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 099d1fc933d2..d629d66d1c31 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -159,7 +159,7 @@ oneway interface IStatusBar
/**
* Used to notify the authentication dialog that a biometric has been authenticated.
*/
- void onBiometricAuthenticated();
+ void onBiometricAuthenticated(int modality);
/**
* Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc.
*/
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index dcc1a7626a1b..9163b6d6215e 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -125,7 +125,7 @@ interface IStatusBarService
int multiSensorConfig);
// Used to notify the authentication dialog that a biometric has been authenticated
- void onBiometricAuthenticated();
+ void onBiometricAuthenticated(int modality);
// Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
void onBiometricHelp(int modality, String message);
// Used to show an error - the dialog will dismiss after a certain amount of time
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 597167026d19..5b7092cabfcb 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -346,7 +346,7 @@ enum RuntimeFlags : uint32_t {
GWP_ASAN_LEVEL_NEVER = 0 << 21,
GWP_ASAN_LEVEL_LOTTERY = 1 << 21,
GWP_ASAN_LEVEL_ALWAYS = 2 << 21,
- NATIVE_HEAP_ZERO_INIT = 1 << 23,
+ NATIVE_HEAP_ZERO_INIT_ENABLED = 1 << 23,
PROFILEABLE = 1 << 24,
};
@@ -1709,13 +1709,13 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
// would be nice to have them for apps, we will have to wait until they are
// proven out, have more efficient hardware, and/or apply them only to new
// applications.
- if (!(runtime_flags & RuntimeFlags::NATIVE_HEAP_ZERO_INIT)) {
+ if (!(runtime_flags & RuntimeFlags::NATIVE_HEAP_ZERO_INIT_ENABLED)) {
mallopt(M_BIONIC_ZERO_INIT, 0);
}
// Now that we've used the flag, clear it so that we don't pass unknown flags to the ART
// runtime.
- runtime_flags &= ~RuntimeFlags::NATIVE_HEAP_ZERO_INIT;
+ runtime_flags &= ~RuntimeFlags::NATIVE_HEAP_ZERO_INIT_ENABLED;
bool forceEnableGwpAsan = false;
switch (runtime_flags & RuntimeFlags::GWP_ASAN_LEVEL_MASK) {
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 4c0ba8cbe304..6a421f007a76 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -385,7 +385,18 @@ message SecureSettingsProto {
optional SettingProto multi_press_timeout = 38 [ (android.privacy).dest = DEST_AUTOMATIC ];
- optional SettingProto nav_bar_kids_mode = 91 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ message NavBar {
+ option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+ // Nav bar is forced to always be visible, even in immersive mode.
+ optional SettingProto nav_bar_force_visible = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ // Indicates whether the device is in kids nav mode.
+ optional SettingProto nav_bar_kids_mode = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ }
+ optional NavBar nav_bar = 92;
+ reserved 91; // Formerly nav_bar_kids_mode
+ reserved "nav_bar_kids_mode"; // Moved to message NavBar
+
optional SettingProto navigation_mode = 76 [ (android.privacy).dest = DEST_AUTOMATIC ];
message NfcPayment {
@@ -668,5 +679,5 @@ message SecureSettingsProto {
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 92;
+ // Next tag = 93;
}
diff --git a/core/proto/android/server/biometrics.proto b/core/proto/android/server/biometrics.proto
index fc9da90ccd59..ac9e3e001d50 100644
--- a/core/proto/android/server/biometrics.proto
+++ b/core/proto/android/server/biometrics.proto
@@ -91,23 +91,9 @@ message BiometricServiceStateProto {
STATE_CLIENT_DIED_CANCELLING = 10;
}
- enum MultiSensorState {
- // Initializing or not yet started.
- MULTI_SENSOR_STATE_UNKNOWN = 0;
- // Sensors are in the process of being transitioned and there is no active sensor.
- MULTI_SENSOR_STATE_SWITCHING = 1;
- // Face sensor is being used as the primary input.
- MULTI_SENSOR_STATE_FACE_SCANNING = 2;
- // Fingerprint sensor is being used as the primary input.
- MULTI_SENSOR_STATE_FP_SCANNING = 3;
- }
-
repeated SensorServiceStateProto sensor_service_states = 1;
optional AuthSessionState auth_session_state = 2;
-
- // Additional session state information, when the device has multiple sensors.
- optional MultiSensorState auth_session_multi_sensor_state = 3;
}
// Overall state for an instance of a <Biometric>Service, for example FingerprintService or
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index e8f7b9343746..3929027a4e94 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -446,6 +446,7 @@ message WindowStateProto {
optional bool has_compat_scale = 43;
optional float global_scale = 44;
repeated .android.graphics.RectProto keep_clear_areas = 45;
+ repeated .android.graphics.RectProto unrestricted_keep_clear_areas = 46;
}
message IdentifierProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 73cdaba24789..d7744e377f12 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6926,6 +6926,10 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
+ <service android:name="com.android.server.BinaryTransparencyService$UpdateMeasurementsJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
<service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
android:exported="false">
<intent-filter>
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 23ec3ead959f..cf78646a466f 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -1307,55 +1307,6 @@ public class ChooserActivityTest {
is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST));
}
- /**
- * The case when AppPrediction service is not defined in PackageManager is already covered
- * as a test parameter {@link ChooserActivityTest#packageManagers}. This test is checking the
- * case when the prediction service is defined but the component is not available on the device.
- */
- @Test
- public void testIsAppPredictionServiceAvailable() {
- Intent sendIntent = createSendTextIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
- when(
- ChooserActivityOverrideData
- .getInstance()
- .resolverListController
- .getResolversForIntent(
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class)))
- .thenReturn(resolvedComponentInfos);
-
- final ChooserActivity activity =
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
- if (activity.getPackageManager().getAppPredictionServicePackageName() == null) {
- assertThat(activity.isAppPredictionServiceAvailable(), is(false));
- } else {
- if (!shouldTestTogglingAppPredictionServiceAvailabilityAtRuntime()) {
- return;
- }
-
- // This isn't a toggle per-se, but isAppPredictionServiceAvailable only works in
- // system (see comment in the method).
- assertThat(activity.isAppPredictionServiceAvailable(), is(true));
-
- ChooserActivityOverrideData.getInstance().resources =
- Mockito.spy(activity.getResources());
- when(
- ChooserActivityOverrideData
- .getInstance()
- .resources
- .getString(
- getRuntimeResourceId(
- "config_defaultAppPredictionService",
- "string")))
- .thenReturn("ComponentNameThatDoesNotExist");
-
- assertThat(activity.isAppPredictionServiceAvailable(), is(false));
- }
- }
-
@Test
public void testConvertToChooserTarget_predictionService() {
Intent sendIntent = createSendTextIntent();
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 33b09b8831ce..55f205bb14a6 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -690,6 +690,14 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
}
}
+ /**
+ * Gets the total duration of the animation
+ * @hide
+ */
+ public long getTotalDuration() {
+ return mAnimatorSet.getTotalDuration();
+ }
+
private static class AnimatedVectorDrawableState extends ConstantState {
@Config int mChangingConfigurations;
VectorDrawable mVectorDrawable;
@@ -1074,6 +1082,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
boolean isInfinite();
void pause();
void resume();
+ long getTotalDuration();
}
private static class VectorDrawableAnimatorUI implements VectorDrawableAnimator {
@@ -1085,6 +1094,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
// setup by init().
private ArrayList<AnimatorListener> mListenerArray = null;
private boolean mIsInfinite = false;
+ private long mTotalDuration;
VectorDrawableAnimatorUI(@NonNull AnimatedVectorDrawable drawable) {
mDrawable = drawable;
@@ -1100,7 +1110,8 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
// Keep a deep copy of the set, such that set can be still be constantly representing
// the static content from XML file.
mSet = set.clone();
- mIsInfinite = mSet.getTotalDuration() == Animator.DURATION_INFINITE;
+ mTotalDuration = mSet.getTotalDuration();
+ mIsInfinite = mTotalDuration == Animator.DURATION_INFINITE;
// If there are listeners added before calling init(), now they should be setup.
if (mListenerArray != null && !mListenerArray.isEmpty()) {
@@ -1219,6 +1230,11 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
private void invalidateOwningView() {
mDrawable.invalidateSelf();
}
+
+ @Override
+ public long getTotalDuration() {
+ return mTotalDuration;
+ }
}
/**
@@ -1249,6 +1265,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
private int mLastListenerId = 0;
private final IntArray mPendingAnimationActions = new IntArray();
private final AnimatedVectorDrawable mDrawable;
+ private long mTotalDuration;
VectorDrawableAnimatorRT(AnimatedVectorDrawable drawable) {
mDrawable = drawable;
@@ -1270,7 +1287,8 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
.getNativeTree();
nSetVectorDrawableTarget(mSetPtr, vectorDrawableTreePtr);
mInitialized = true;
- mIsInfinite = set.getTotalDuration() == Animator.DURATION_INFINITE;
+ mTotalDuration = set.getTotalDuration();
+ mIsInfinite = mTotalDuration == Animator.DURATION_INFINITE;
// Check reversible.
mIsReversible = true;
@@ -1796,6 +1814,11 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
}
mPendingAnimationActions.clear();
}
+
+ @Override
+ public long getTotalDuration() {
+ return mTotalDuration;
+ }
}
private static native long nCreateAnimatorSet();
diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java
index 8c3fa441cbb0..7fd2201e7108 100644
--- a/graphics/java/android/graphics/drawable/AnimationDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java
@@ -424,6 +424,17 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An
System.arraycopy(mDurations, 0, newDurations, 0, oldSize);
mDurations = newDurations;
}
+
+ public long getTotalDuration() {
+ if (mDurations != null) {
+ int total = 0;
+ for (int dur : mDurations) {
+ total += dur;
+ }
+ return total;
+ }
+ return 0;
+ }
}
@Override
@@ -435,6 +446,14 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An
}
}
+ /**
+ * Gets the total duration of the animation
+ * @hide
+ */
+ public long getTotalDuration() {
+ return mAnimationState.getTotalDuration();
+ }
+
private AnimationDrawable(AnimationState state, Resources res) {
final AnimationState as = new AnimationState(state, this, res);
setConstantState(as);
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index 2e85b304ec47..31dd10a8ed53 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -66,6 +66,7 @@ import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.ECGenParameterSpec;
+import java.security.spec.NamedParameterSpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.util.ArrayList;
import java.util.Arrays;
@@ -119,36 +120,42 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
private static final int RSA_MIN_KEY_SIZE = 512;
private static final int RSA_MAX_KEY_SIZE = 8192;
- private static final Map<String, Integer> SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE =
+ private static final Map<String, Integer> SUPPORTED_EC_CURVE_NAME_TO_SIZE =
new HashMap<String, Integer>();
- private static final List<String> SUPPORTED_EC_NIST_CURVE_NAMES = new ArrayList<String>();
- private static final List<Integer> SUPPORTED_EC_NIST_CURVE_SIZES = new ArrayList<Integer>();
+ private static final List<String> SUPPORTED_EC_CURVE_NAMES = new ArrayList<String>();
+ private static final List<Integer> SUPPORTED_EC_CURVE_SIZES = new ArrayList<Integer>();
+ private static final String CURVE_X_25519 = NamedParameterSpec.X25519.getName();
+ private static final String CURVE_ED_25519 = NamedParameterSpec.ED25519.getName();
+
static {
// Aliases for NIST P-224
- SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-224", 224);
- SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp224r1", 224);
+ SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("p-224", 224);
+ SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("secp224r1", 224);
// Aliases for NIST P-256
- SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-256", 256);
- SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp256r1", 256);
- SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("prime256v1", 256);
+ SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("p-256", 256);
+ SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("secp256r1", 256);
+ SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("prime256v1", 256);
+ // Aliases for Curve 25519
+ SUPPORTED_EC_CURVE_NAME_TO_SIZE.put(CURVE_X_25519.toLowerCase(Locale.US), 256);
+ SUPPORTED_EC_CURVE_NAME_TO_SIZE.put(CURVE_ED_25519.toLowerCase(Locale.US), 256);
// Aliases for NIST P-384
- SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-384", 384);
- SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp384r1", 384);
+ SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("p-384", 384);
+ SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("secp384r1", 384);
// Aliases for NIST P-521
- SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-521", 521);
- SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp521r1", 521);
+ SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("p-521", 521);
+ SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("secp521r1", 521);
- SUPPORTED_EC_NIST_CURVE_NAMES.addAll(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.keySet());
- Collections.sort(SUPPORTED_EC_NIST_CURVE_NAMES);
+ SUPPORTED_EC_CURVE_NAMES.addAll(SUPPORTED_EC_CURVE_NAME_TO_SIZE.keySet());
+ Collections.sort(SUPPORTED_EC_CURVE_NAMES);
- SUPPORTED_EC_NIST_CURVE_SIZES.addAll(
- new HashSet<Integer>(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.values()));
- Collections.sort(SUPPORTED_EC_NIST_CURVE_SIZES);
+ SUPPORTED_EC_CURVE_SIZES.addAll(
+ new HashSet<Integer>(SUPPORTED_EC_CURVE_NAME_TO_SIZE.values()));
+ Collections.sort(SUPPORTED_EC_CURVE_SIZES);
}
private final int mOriginalKeymasterAlgorithm;
@@ -164,6 +171,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
private int mKeySizeBits;
private SecureRandom mRng;
private KeyDescriptor mAttestKeyDescriptor;
+ private String mEcCurveName;
private int[] mKeymasterPurposes;
private int[] mKeymasterBlockModes;
@@ -177,12 +185,15 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
mOriginalKeymasterAlgorithm = keymasterAlgorithm;
}
- private @EcCurve int keySize2EcCurve(int keySizeBits)
+ private static @EcCurve int keySizeAndNameToEcCurve(int keySizeBits, String ecCurveName)
throws InvalidAlgorithmParameterException {
switch (keySizeBits) {
case 224:
return EcCurve.P_224;
case 256:
+ if (isCurve25519(ecCurveName)) {
+ return EcCurve.CURVE_25519;
+ }
return EcCurve.P_256;
case 384:
return EcCurve.P_384;
@@ -247,7 +258,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
if (mKeySizeBits == -1) {
mKeySizeBits = getDefaultKeySize(keymasterAlgorithm);
}
- checkValidKeySize(keymasterAlgorithm, mKeySizeBits, mSpec.isStrongBoxBacked());
+ checkValidKeySize(keymasterAlgorithm, mKeySizeBits, mSpec.isStrongBoxBacked(),
+ mEcCurveName);
if (spec.getKeystoreAlias() == null) {
throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided");
@@ -299,6 +311,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
mAttestKeyDescriptor = buildAndCheckAttestKeyDescriptor(spec);
checkAttestKeyPurpose(spec);
+ checkCorrectKeyPurposeForCurve(spec);
success = true;
} finally {
@@ -317,6 +330,42 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
}
}
+ private void checkCorrectKeyPurposeForCurve(KeyGenParameterSpec spec)
+ throws InvalidAlgorithmParameterException {
+ // Validate the key usage purposes against the curve. x25519 should be
+ // key exchange only, ed25519 signing and attesting.
+
+ if (!isCurve25519(mEcCurveName)) {
+ return;
+ }
+
+ if (mEcCurveName.equalsIgnoreCase(CURVE_X_25519)
+ && spec.getPurposes() != KeyProperties.PURPOSE_AGREE_KEY) {
+ throw new InvalidAlgorithmParameterException(
+ "x25519 may only be used for key agreement.");
+ } else if (mEcCurveName.equalsIgnoreCase(CURVE_ED_25519)
+ && !hasOnlyAllowedPurposeForEd25519(spec.getPurposes())) {
+ throw new InvalidAlgorithmParameterException(
+ "ed25519 may not be used for key agreement.");
+ }
+ }
+
+ private static boolean isCurve25519(String ecCurveName) {
+ if (ecCurveName == null) {
+ return false;
+ }
+ return ecCurveName.equalsIgnoreCase(CURVE_X_25519)
+ || ecCurveName.equalsIgnoreCase(CURVE_ED_25519);
+ }
+
+ private static boolean hasOnlyAllowedPurposeForEd25519(@KeyProperties.PurposeEnum int purpose) {
+ final int allowedPurposes = KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY
+ | KeyProperties.PURPOSE_ATTEST_KEY;
+ boolean hasAllowedPurpose = (purpose & allowedPurposes) != 0;
+ boolean hasDisallowedPurpose = (purpose & ~allowedPurposes) != 0;
+ return hasAllowedPurpose && !hasDisallowedPurpose;
+ }
+
private KeyDescriptor buildAndCheckAttestKeyDescriptor(KeyGenParameterSpec spec)
throws InvalidAlgorithmParameterException {
if (spec.getAttestKeyAlias() != null) {
@@ -473,6 +522,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
mRSAPublicExponent = null;
mRng = null;
mKeyStore = null;
+ mEcCurveName = null;
}
private void initAlgorithmSpecificParameters() throws InvalidAlgorithmParameterException {
@@ -514,13 +564,13 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
case KeymasterDefs.KM_ALGORITHM_EC:
if (algSpecificSpec instanceof ECGenParameterSpec) {
ECGenParameterSpec ecSpec = (ECGenParameterSpec) algSpecificSpec;
- String curveName = ecSpec.getName();
- Integer ecSpecKeySizeBits = SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.get(
- curveName.toLowerCase(Locale.US));
+ mEcCurveName = ecSpec.getName();
+ final Integer ecSpecKeySizeBits = SUPPORTED_EC_CURVE_NAME_TO_SIZE.get(
+ mEcCurveName.toLowerCase(Locale.US));
if (ecSpecKeySizeBits == null) {
throw new InvalidAlgorithmParameterException(
- "Unsupported EC curve name: " + curveName
- + ". Supported: " + SUPPORTED_EC_NIST_CURVE_NAMES);
+ "Unsupported EC curve name: " + mEcCurveName
+ + ". Supported: " + SUPPORTED_EC_CURVE_NAMES);
}
if (mKeySizeBits == -1) {
mKeySizeBits = ecSpecKeySizeBits;
@@ -744,7 +794,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_EC) {
params.add(KeyStore2ParameterUtils.makeEnum(
- Tag.EC_CURVE, keySize2EcCurve(mKeySizeBits)
+ Tag.EC_CURVE, keySizeAndNameToEcCurve(mKeySizeBits, mEcCurveName)
));
}
@@ -864,7 +914,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
private static void checkValidKeySize(
int keymasterAlgorithm,
int keySize,
- boolean isStrongBoxBacked)
+ boolean isStrongBoxBacked,
+ String mEcCurveName)
throws InvalidAlgorithmParameterException {
switch (keymasterAlgorithm) {
case KeymasterDefs.KM_ALGORITHM_EC:
@@ -873,9 +924,13 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
"Unsupported StrongBox EC key size: "
+ keySize + " bits. Supported: 256");
}
- if (!SUPPORTED_EC_NIST_CURVE_SIZES.contains(keySize)) {
+ if (isStrongBoxBacked && isCurve25519(mEcCurveName)) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported StrongBox EC: " + mEcCurveName);
+ }
+ if (!SUPPORTED_EC_CURVE_SIZES.contains(keySize)) {
throw new InvalidAlgorithmParameterException("Unsupported EC key size: "
- + keySize + " bits. Supported: " + SUPPORTED_EC_NIST_CURVE_SIZES);
+ + keySize + " bits. Supported: " + SUPPORTED_EC_CURVE_SIZES);
}
break;
case KeymasterDefs.KM_ALGORITHM_RSA:
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
index 72a145fa6a05..358104fffbf6 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
@@ -66,6 +66,11 @@ public class AndroidKeyStoreProvider extends Provider {
private static final String DESEDE_SYSTEM_PROPERTY =
"ro.hardware.keystore_desede";
+ // Conscrypt returns the Ed25519 OID as the JCA key algorithm.
+ private static final String ED25519_OID = "1.3.101.112";
+ // Conscrypt returns "XDH" as the X25519 JCA key algorithm.
+ private static final String X25519_ALIAS = "XDH";
+
/** @hide **/
public AndroidKeyStoreProvider() {
super(PROVIDER_NAME, 1.0, "Android KeyStore security provider");
@@ -78,10 +83,16 @@ public class AndroidKeyStoreProvider extends Provider {
// java.security.KeyPairGenerator
put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC");
put("KeyPairGenerator.RSA", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA");
+ put("KeyPairGenerator." + X25519_ALIAS,
+ PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA");
+ put("KeyPairGenerator." + ED25519_OID,
+ PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA");
// java.security.KeyFactory
putKeyFactoryImpl("EC");
putKeyFactoryImpl("RSA");
+ putKeyFactoryImpl(X25519_ALIAS);
+ putKeyFactoryImpl(ED25519_OID);
// javax.crypto.KeyGenerator
put("KeyGenerator.AES", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$AES");
@@ -219,12 +230,17 @@ public class AndroidKeyStoreProvider extends Provider {
KeyStoreSecurityLevel securityLevel = iSecurityLevel;
if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(jcaKeyAlgorithm)) {
-
return new AndroidKeyStoreECPublicKey(descriptor, metadata,
iSecurityLevel, (ECPublicKey) publicKey);
} else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(jcaKeyAlgorithm)) {
return new AndroidKeyStoreRSAPublicKey(descriptor, metadata,
iSecurityLevel, (RSAPublicKey) publicKey);
+ } else if (ED25519_OID.equalsIgnoreCase(jcaKeyAlgorithm)) {
+ //TODO(b/214203951) missing classes in conscrypt
+ throw new ProviderException("Curve " + ED25519_OID + " not supported yet");
+ } else if (X25519_ALIAS.equalsIgnoreCase(jcaKeyAlgorithm)) {
+ //TODO(b/214203951) missing classes in conscrypt
+ throw new ProviderException("Curve " + X25519_ALIAS + " not supported yet");
} else {
throw new ProviderException("Unsupported Android Keystore public key algorithm: "
+ jcaKeyAlgorithm);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 9b414681c0fa..8d5fdfbc8cb0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -52,6 +52,9 @@ import com.android.wm.shell.common.annotations.ShellMainThread;
*/
public class BackAnimationController implements RemoteCallable<BackAnimationController> {
+ private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability";
+ public static final boolean IS_ENABLED = SystemProperties
+ .getInt(BACK_PREDICTABILITY_PROP, 0) > 0;
private static final String BACK_PREDICTABILITY_PROGRESS_THRESHOLD_PROP =
"persist.debug.back_predictability_progress_threshold";
private static final int PROGRESS_THRESHOLD = SystemProperties
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index c6a68dc98da6..58f79f3600de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1608,7 +1608,9 @@ public class BubbleStackView extends FrameLayout
Log.d(TAG, "addBubble: " + bubble);
}
- if (getBubbleCount() == 0 && shouldShowStackEdu()) {
+ final boolean firstBubble = getBubbleCount() == 0;
+
+ if (firstBubble && shouldShowStackEdu()) {
// Override the default stack position if we're showing user education.
mStackAnimationController.setStackPosition(mPositioner.getDefaultStartPosition());
}
@@ -1621,7 +1623,7 @@ public class BubbleStackView extends FrameLayout
new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
mPositioner.getBubbleSize()));
- if (getBubbleCount() == 0) {
+ if (firstBubble) {
mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
}
// Set the dot position to the opposite of the side the stack is resting on, since the stack
@@ -1652,6 +1654,10 @@ public class BubbleStackView extends FrameLayout
bubble.cleanupViews();
}
updateExpandedView();
+ if (getBubbleCount() == 0 && !isExpanded()) {
+ // This is the last bubble and the stack is collapsed
+ updateStackPosition();
+ }
logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 8a482fbfa1c4..b52c8d118711 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -502,16 +502,24 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) {
wct.setBounds(task1.token, mBounds1);
+ wct.setSmallestScreenWidthDp(task1.token, getSmallestWidthDp(mBounds1));
mWinBounds1.set(mBounds1);
mWinToken1 = task1.token;
}
if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) {
wct.setBounds(task2.token, mBounds2);
+ wct.setSmallestScreenWidthDp(task2.token, getSmallestWidthDp(mBounds2));
mWinBounds2.set(mBounds2);
mWinToken2 = task2.token;
}
}
+ private int getSmallestWidthDp(Rect bounds) {
+ final int minWidth = Math.min(bounds.width(), bounds.height());
+ final float density = mContext.getResources().getDisplayMetrics().density;
+ return (int) (minWidth / density);
+ }
+
/**
* Shift configuration bounds to prevent client apps get configuration changed or relaunch. And
* restore shifted configuration bounds if it's no longer shifted.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index c94f3d197dea..0362b3fba36b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -699,7 +699,10 @@ public abstract class WMShellBaseModule {
Context context,
@ShellMainThread ShellExecutor shellExecutor
) {
- return Optional.of(
- new BackAnimationController(shellExecutor, context));
+ if (BackAnimationController.IS_ENABLED) {
+ return Optional.of(
+ new BackAnimationController(shellExecutor, context));
+ }
+ return Optional.empty();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
index 082fe9205be8..22dd9b953be7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
@@ -18,7 +18,6 @@ package com.android.wm.shell.splitscreen;
import android.annotation.Nullable;
import android.content.Context;
-import android.graphics.Rect;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -49,14 +48,13 @@ class MainStage extends StageTaskListener {
return mIsActive;
}
- void activate(Rect rootBounds, WindowContainerTransaction wct, boolean includingTopTask) {
+ void activate(WindowContainerTransaction wct, boolean includingTopTask) {
if (mIsActive) return;
final WindowContainerToken rootToken = mRootTaskInfo.token;
- wct.setBounds(rootToken, rootBounds)
- // Moving the root task to top after the child tasks were re-parented , or the root
- // task cannot be visible and focused.
- .reorder(rootToken, true /* onTop */);
+ // Moving the root task to top after the child tasks were re-parented , or the root
+ // task cannot be visible and focused.
+ wct.reorder(rootToken, true /* onTop */);
if (includingTopTask) {
wct.reparentTasks(
null /* currentParent */,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 76641f0e6c21..7318f4860b1c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -354,8 +354,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.setDivideRatio(splitRatio);
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
- mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);
- mSideStage.setBounds(getSideStageBounds(), wct);
+ mMainStage.activate(wct, false /* reparent */);
+ updateWindowBounds(mSplitLayout, wct);
// Make sure the launch options will put tasks in the corresponding split roots
addActivityOptions(mainOptions, mMainStage);
@@ -470,13 +470,14 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.setDivideRatio(splitRatio);
if (mMainStage.isActive()) {
- mMainStage.moveToTop(getMainStageBounds(), wct);
+ mMainStage.moveToTop(wct);
} else {
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
- mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);
+ mMainStage.activate(wct, false /* reparent */);
}
- mSideStage.moveToTop(getSideStageBounds(), wct);
+ mSideStage.moveToTop(wct);
+ updateWindowBounds(mSplitLayout, wct);
// Make sure the launch options will put tasks in the corresponding split roots
addActivityOptions(mainOptions, mMainStage);
@@ -774,8 +775,9 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
setSideStagePosition(startPosition, wct);
mSideStage.addTask(taskInfo, wct);
}
- mMainStage.activate(getMainStageBounds(), wct, true /* includingTopTask */);
- mSideStage.moveToTop(getSideStageBounds(), wct);
+ mMainStage.activate(wct, true /* includingTopTask */);
+ mSideStage.moveToTop(wct);
+ updateWindowBounds(mSplitLayout, wct);
}
void finishEnterSplitScreen(SurfaceControl.Transaction t) {
@@ -997,10 +999,9 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Exit to side stage if main stage no longer has children.
exitSplitScreen(mSideStage, EXIT_REASON_APP_FINISHED);
}
- } else if (isSideStage) {
+ } else if (isSideStage && !mMainStage.isActive()) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
mSplitLayout.init();
- // Make sure the main stage is active.
prepareEnterSplitScreen(wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 83534c178469..c5aab45f56fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -53,8 +53,8 @@ import java.io.PrintWriter;
* Base class that handle common task org. related for split-screen stages.
* Note that this class and its sub-class do not directly perform hierarchy operations.
* They only serve to hold a collection of tasks and provide APIs like
- * {@link #setBounds(Rect, WindowContainerTransaction)} for the centralized {@link StageCoordinator}
- * to perform operations in-sync with other containers.
+ * {@link #addTask(ActivityManager.RunningTaskInfo, WindowContainerTransaction)} for the centralized
+ * {@link StageCoordinator} to perform hierarchy operations in-sync with other containers.
*
* @see StageCoordinator
*/
@@ -310,13 +310,9 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
wct.reparent(task.token, mRootTaskInfo.token, true /* onTop*/);
}
- void moveToTop(Rect rootBounds, WindowContainerTransaction wct) {
+ void moveToTop(WindowContainerTransaction wct) {
final WindowContainerToken rootToken = mRootTaskInfo.token;
- wct.setBounds(rootToken, rootBounds).reorder(rootToken, true /* onTop */);
- }
-
- void setBounds(Rect bounds, WindowContainerTransaction wct) {
- wct.setBounds(mRootTaskInfo.token, bounds);
+ wct.reorder(rootToken, true /* onTop */);
}
void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 025bcad07955..33aa018923a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -69,6 +69,7 @@ import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -115,8 +116,10 @@ public class SplashscreenContentDrawer {
private final Handler mSplashscreenWorkerHandler;
@VisibleForTesting
final ColorCache mColorCache;
+ private final ShellExecutor mSplashScreenExecutor;
- SplashscreenContentDrawer(Context context, IconProvider iconProvider, TransactionPool pool) {
+ SplashscreenContentDrawer(Context context, IconProvider iconProvider, TransactionPool pool,
+ ShellExecutor splashScreenExecutor) {
mContext = context;
mIconProvider = iconProvider;
mTransactionPool = pool;
@@ -129,6 +132,7 @@ public class SplashscreenContentDrawer {
shellSplashscreenWorkerThread.start();
mSplashscreenWorkerHandler = shellSplashscreenWorkerThread.getThreadHandler();
mColorCache = new ColorCache(mContext, mSplashscreenWorkerHandler);
+ mSplashScreenExecutor = splashScreenExecutor;
}
/**
@@ -397,7 +401,7 @@ public class SplashscreenContentDrawer {
SplashScreenView build() {
Drawable iconDrawable;
- final int animationDuration;
+ final long animationDuration;
if (mSuggestType == STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN
|| mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
// empty or legacy splash screen case
@@ -455,8 +459,8 @@ public class SplashscreenContentDrawer {
iconDrawable, mDefaultIconSize, mFinalIconSize, mSplashscreenWorkerHandler);
} else {
mFinalIconDrawables = SplashscreenIconDrawableFactory.makeIconDrawable(
- mTmpAttrs.mIconBgColor, mThemeColor,
- iconDrawable, mDefaultIconSize, mFinalIconSize, mSplashscreenWorkerHandler);
+ mTmpAttrs.mIconBgColor, mThemeColor, iconDrawable, mDefaultIconSize,
+ mFinalIconSize, mSplashscreenWorkerHandler, mSplashScreenExecutor);
}
}
@@ -516,7 +520,7 @@ public class SplashscreenContentDrawer {
}
private SplashScreenView fillViewWithIcon(int iconSize, @Nullable Drawable[] iconDrawable,
- int animationDuration, Consumer<Runnable> uiThreadInitTask) {
+ long animationDuration, Consumer<Runnable> uiThreadInitTask) {
Drawable foreground = null;
Drawable background = null;
if (iconDrawable != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
index 54281e0199e3..fdd5a1578f41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
@@ -18,9 +18,7 @@ package com.android.wm.shell.startingsurface;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -36,6 +34,8 @@ import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Animatable;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Trace;
@@ -44,6 +44,9 @@ import android.util.PathParser;
import android.window.SplashScreenView;
import com.android.internal.R;
+import com.android.wm.shell.common.ShellExecutor;
+
+import java.util.function.LongConsumer;
/**
* Creating a lightweight Drawable object used for splash screen.
@@ -60,14 +63,15 @@ public class SplashscreenIconDrawableFactory {
*/
static Drawable[] makeIconDrawable(@ColorInt int backgroundColor, @ColorInt int themeColor,
@NonNull Drawable foregroundDrawable, int srcIconSize, int iconSize,
- Handler splashscreenWorkerHandler) {
+ Handler splashscreenWorkerHandler, ShellExecutor splashScreenExecutor) {
Drawable foreground;
Drawable background = null;
boolean drawBackground =
backgroundColor != Color.TRANSPARENT && backgroundColor != themeColor;
if (foregroundDrawable instanceof Animatable) {
- foreground = new AnimatableIconAnimateListener(foregroundDrawable);
+ foreground = new AnimatableIconAnimateListener(foregroundDrawable,
+ splashScreenExecutor);
} else if (foregroundDrawable instanceof AdaptiveIconDrawable) {
// If the icon is Adaptive, we already use the icon background.
drawBackground = false;
@@ -266,99 +270,107 @@ public class SplashscreenIconDrawableFactory {
*/
public static class AnimatableIconAnimateListener extends AdaptiveForegroundDrawable
implements SplashScreenView.IconAnimateListener {
- private Animatable mAnimatableIcon;
- private Animator mIconAnimator;
+ private final Animatable mAnimatableIcon;
private boolean mAnimationTriggered;
private AnimatorListenerAdapter mJankMonitoringListener;
+ private boolean mRunning;
+ private long mDuration;
+ private LongConsumer mStartListener;
+ private final ShellExecutor mSplashScreenExecutor;
- AnimatableIconAnimateListener(@NonNull Drawable foregroundDrawable) {
+ AnimatableIconAnimateListener(@NonNull Drawable foregroundDrawable,
+ ShellExecutor splashScreenExecutor) {
super(foregroundDrawable);
- mForegroundDrawable.setCallback(mCallback);
- }
-
- @Override
- public void setAnimationJankMonitoring(AnimatorListenerAdapter listener) {
- mJankMonitoringListener = listener;
- }
-
- @Override
- public boolean prepareAnimate(long duration, Runnable startListener) {
- mAnimatableIcon = (Animatable) mForegroundDrawable;
- mIconAnimator = ValueAnimator.ofInt(0, 1);
- mIconAnimator.setDuration(duration);
- mIconAnimator.addListener(new Animator.AnimatorListener() {
+ Callback callback = new Callback() {
@Override
- public void onAnimationStart(Animator animation) {
- if (startListener != null) {
- startListener.run();
- }
- try {
- if (mJankMonitoringListener != null) {
- mJankMonitoringListener.onAnimationStart(animation);
- }
- mAnimatableIcon.start();
- } catch (Exception ex) {
- Log.e(TAG, "Error while running the splash screen animated icon", ex);
- animation.cancel();
- }
+ public void invalidateDrawable(@NonNull Drawable who) {
+ invalidateSelf();
}
@Override
- public void onAnimationEnd(Animator animation) {
- mAnimatableIcon.stop();
- if (mJankMonitoringListener != null) {
- mJankMonitoringListener.onAnimationEnd(animation);
- }
+ public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what,
+ long when) {
+ scheduleSelf(what, when);
}
@Override
- public void onAnimationCancel(Animator animation) {
- mAnimatableIcon.stop();
- if (mJankMonitoringListener != null) {
- mJankMonitoringListener.onAnimationCancel(animation);
- }
+ public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
+ unscheduleSelf(what);
}
+ };
+ mForegroundDrawable.setCallback(callback);
+ mSplashScreenExecutor = splashScreenExecutor;
+ mAnimatableIcon = (Animatable) mForegroundDrawable;
+ }
- @Override
- public void onAnimationRepeat(Animator animation) {
- // do not repeat
- mAnimatableIcon.stop();
- }
- });
- return true;
+ @Override
+ public void setAnimationJankMonitoring(AnimatorListenerAdapter listener) {
+ mJankMonitoringListener = listener;
}
@Override
- public void stopAnimation() {
- if (mIconAnimator != null && mIconAnimator.isRunning()) {
- mIconAnimator.end();
- mJankMonitoringListener = null;
- }
+ public void prepareAnimate(long duration, LongConsumer startListener) {
+ stopAnimation();
+ mDuration = duration;
+ mStartListener = startListener;
}
- private final Callback mCallback = new Callback() {
- @Override
- public void invalidateDrawable(@NonNull Drawable who) {
- invalidateSelf();
+ private void startAnimation() {
+ if (mJankMonitoringListener != null) {
+ mJankMonitoringListener.onAnimationStart(null);
+ }
+ try {
+ mAnimatableIcon.start();
+ } catch (Exception ex) {
+ Log.e(TAG, "Error while running the splash screen animated icon", ex);
+ mRunning = false;
+ if (mJankMonitoringListener != null) {
+ mJankMonitoringListener.onAnimationCancel(null);
+ }
+ if (mStartListener != null) {
+ mStartListener.accept(mDuration);
+ }
+ return;
}
+ long animDuration = mDuration;
+ if (mAnimatableIcon instanceof AnimatedVectorDrawable
+ && ((AnimatedVectorDrawable) mAnimatableIcon).getTotalDuration() > 0) {
+ animDuration = ((AnimatedVectorDrawable) mAnimatableIcon).getTotalDuration();
+ } else if (mAnimatableIcon instanceof AnimationDrawable
+ && ((AnimationDrawable) mAnimatableIcon).getTotalDuration() > 0) {
+ animDuration = ((AnimationDrawable) mAnimatableIcon).getTotalDuration();
+ }
+ mRunning = true;
+ mSplashScreenExecutor.executeDelayed(this::stopAnimation, animDuration);
+ if (mStartListener != null) {
+ mStartListener.accept(Math.max(animDuration, 0));
+ }
+ }
- @Override
- public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
- scheduleSelf(what, when);
+ private void onAnimationEnd() {
+ mAnimatableIcon.stop();
+ if (mJankMonitoringListener != null) {
+ mJankMonitoringListener.onAnimationEnd(null);
}
+ mStartListener = null;
+ mRunning = false;
+ }
- @Override
- public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
- unscheduleSelf(what);
+ @Override
+ public void stopAnimation() {
+ if (mRunning) {
+ mSplashScreenExecutor.removeCallbacks(this::stopAnimation);
+ onAnimationEnd();
+ mJankMonitoringListener = null;
}
- };
+ }
private void ensureAnimationStarted() {
if (mAnimationTriggered) {
return;
}
- if (mIconAnimator != null && !mIconAnimator.isRunning()) {
- mIconAnimator.start();
+ if (!mRunning) {
+ startAnimation();
}
mAnimationTriggered = true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 61cbf6e3c93c..04d6ef7f9505 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -148,7 +148,8 @@ public class StartingSurfaceDrawer {
mContext = context;
mDisplayManager = mContext.getSystemService(DisplayManager.class);
mSplashScreenExecutor = splashScreenExecutor;
- mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool);
+ mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool,
+ mSplashScreenExecutor);
mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance());
mWindowManagerGlobal = WindowManagerGlobal.getInstance();
mDisplayManager.getDisplay(DEFAULT_DISPLAY);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 83d5f04b7cdb..daec336fc71e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -28,8 +28,10 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.app.ActivityManager;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.window.WindowContainerTransaction;
import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -38,6 +40,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayImeController;
import org.junit.Before;
@@ -56,6 +59,7 @@ public class SplitLayoutTests extends ShellTestCase {
@Mock SplitWindowManager.ParentContainerCallbacks mCallbacks;
@Mock DisplayImeController mDisplayImeController;
@Mock ShellTaskOrganizer mTaskOrganizer;
+ @Mock WindowContainerTransaction mWct;
@Captor ArgumentCaptor<Runnable> mRunnableCaptor;
private SplitLayout mSplitLayout;
@@ -149,6 +153,16 @@ public class SplitLayoutTests extends ShellTestCase {
verify(mSplitLayoutHandler).onSnappedToDismiss(eq(true));
}
+ @Test
+ public void testApplyTaskChanges_updatesSmallestScreenWidthDp() {
+ final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build();
+ final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build();
+ mSplitLayout.applyTaskChanges(mWct, task1, task2);
+
+ verify(mWct).setSmallestScreenWidthDp(eq(task1.token), anyInt());
+ verify(mWct).setSmallestScreenWidthDp(eq(task2.token), anyInt());
+ }
+
private void waitDividerFlingFinished() {
verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), mRunnableCaptor.capture());
mRunnableCaptor.getValue().run();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
index c9720671f49c..0639ad5d0a62 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
@@ -67,8 +67,7 @@ public class MainStageTests extends ShellTestCase {
@Test
public void testActiveDeactivate() {
- mMainStage.activate(mRootTaskInfo.configuration.windowConfiguration.getBounds(), mWct,
- true /* reparent */);
+ mMainStage.activate(mWct, true /* reparent */);
assertThat(mMainStage.isActive()).isTrue();
mMainStage.deactivate(mWct);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 59c377a3e13d..19d2a7ef6f45 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -44,7 +44,6 @@ import static org.mockito.Mockito.mock;
import android.annotation.NonNull;
import android.app.ActivityManager;
-import android.graphics.Rect;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.SurfaceControl;
@@ -421,8 +420,7 @@ public class SplitTransitionTests extends ShellTestCase {
mock(SurfaceControl.Transaction.class),
mock(SurfaceControl.Transaction.class),
mock(Transitions.TransitionFinishCallback.class));
- mMainStage.activate(new Rect(0, 0, 100, 100), new WindowContainerTransaction(),
- true /* includingTopTask */);
+ mMainStage.activate(new WindowContainerTransaction(), true /* includingTopTask */);
}
private boolean containsSplitEnter(@NonNull WindowContainerTransaction wct) {
diff --git a/media/jni/soundpool/Stream.cpp b/media/jni/soundpool/Stream.cpp
index 50bb79ccaa0b..9ed8770a455c 100644
--- a/media/jni/soundpool/Stream.cpp
+++ b/media/jni/soundpool/Stream.cpp
@@ -15,6 +15,7 @@
*/
//#define LOG_NDEBUG 0
+#include <utility>
#define LOG_TAG "SoundPool::Stream"
#include <utils/Log.h>
#include <android/content/AttributionSourceState.h>
@@ -309,13 +310,11 @@ void Stream::play_l(const std::shared_ptr<Sound>& sound, int32_t nextStreamID,
}
if (mAudioTrack == nullptr) {
// mToggle toggles each time a track is started on a given stream.
- // The toggle is concatenated with the Stream address and passed to AudioTrack
- // as callback user data. This enables the detection of callbacks received from the old
+ // This enables the detection of callbacks received from the old
// audio track while the new one is being started and avoids processing them with
// wrong audio audio buffer size (mAudioBufferSize)
auto toggle = mToggle ^ 1;
// NOLINTNEXTLINE(performance-no-int-to-ptr)
- void* userData = reinterpret_cast<void*>((uintptr_t)this | toggle);
audio_channel_mask_t soundChannelMask = sound->getChannelMask();
// When sound contains a valid channel mask, use it as is.
// Otherwise, use stream count to calculate channel mask.
@@ -327,10 +326,11 @@ void Stream::play_l(const std::shared_ptr<Sound>& sound, int32_t nextStreamID,
android::content::AttributionSourceState attributionSource;
attributionSource.packageName = mStreamManager->getOpPackageName();
attributionSource.token = sp<BBinder>::make();
+ mCallback = sp<StreamCallback>::make(this, toggle),
// TODO b/182469354 make consistent with AudioRecord, add util for native source
mAudioTrack = new AudioTrack(streamType, sampleRate, sound->getFormat(),
channelMask, sound->getIMemory(), AUDIO_OUTPUT_FLAG_FAST,
- staticCallback, userData,
+ mCallback,
0 /*default notification frames*/, AUDIO_SESSION_ALLOCATE,
AudioTrack::TRANSFER_DEFAULT,
nullptr /*offloadInfo*/, attributionSource,
@@ -375,16 +375,55 @@ void Stream::play_l(const std::shared_ptr<Sound>& sound, int32_t nextStreamID,
mStreamID = nextStreamID; // prefer this to be the last, as it is an atomic sync point
}
-/* static */
-void Stream::staticCallback(int event, void* user, void* info)
-{
- const auto userAsInt = (uintptr_t)user;
- // NOLINTNEXTLINE(performance-no-int-to-ptr)
- auto stream = reinterpret_cast<Stream*>(userAsInt & ~1);
- stream->callback(event, info, int(userAsInt & 1), 0 /* tries */);
+int Stream::getCorrespondingStreamID() {
+ std::lock_guard lock(mLock);
+ return static_cast<int>(mAudioTrack ? mStreamID : getPairStream()->mStreamID);
+}
+size_t Stream::StreamCallback::onMoreData(const AudioTrack::Buffer&) {
+ ALOGW("%s streamID %d Unexpected EVENT_MORE_DATA for static track",
+ __func__, mStream->getCorrespondingStreamID());
+ return 0;
+}
+
+void Stream::StreamCallback::onUnderrun() {
+ ALOGW("%s streamID %d Unexpected EVENT_UNDERRUN for static track",
+ __func__, mStream->getCorrespondingStreamID());
+}
+
+void Stream::StreamCallback::onLoopEnd(int32_t) {
+ ALOGV("%s streamID %d EVENT_LOOP_END", __func__, mStream->getCorrespondingStreamID());
+}
+
+void Stream::StreamCallback::onMarker(uint32_t) {
+ ALOGW("%s streamID %d Unexpected EVENT_MARKER for static track",
+ __func__, mStream->getCorrespondingStreamID());
+}
+
+void Stream::StreamCallback::onNewPos(uint32_t) {
+ ALOGW("%s streamID %d Unexpected EVENT_NEW_POS for static track",
+ __func__, mStream->getCorrespondingStreamID());
}
-void Stream::callback(int event, void* info, int toggle, int tries)
+void Stream::StreamCallback::onBufferEnd() {
+ mStream->onBufferEnd(mToggle, 0);
+}
+
+void Stream::StreamCallback::onNewIAudioTrack() {
+ ALOGV("%s streamID %d NEW_IAUDIOTRACK", __func__, mStream->getCorrespondingStreamID());
+}
+
+void Stream::StreamCallback::onStreamEnd() {
+ ALOGW("%s streamID %d Unexpected EVENT_STREAM_END for static track",
+ __func__, mStream->getCorrespondingStreamID());
+}
+
+size_t Stream::StreamCallback::onCanWriteMoreData(const AudioTrack::Buffer&) {
+ ALOGW("%s streamID %d Unexpected EVENT_CAN_WRITE_MORE_DATA for static track",
+ __func__, mStream->getCorrespondingStreamID());
+ return 0;
+}
+
+void Stream::onBufferEnd(int toggle, int tries)
{
int32_t activeStreamIDToRestart = 0;
{
@@ -400,7 +439,7 @@ void Stream::callback(int event, void* info, int toggle, int tries)
if (tries < 3) {
lock.unlock();
ALOGV("%s streamID %d going to pair stream", __func__, (int)mStreamID);
- getPairStream()->callback(event, info, toggle, tries + 1);
+ getPairStream()->onBufferEnd(toggle, tries + 1);
} else {
ALOGW("%s streamID %d cannot find track", __func__, (int)mStreamID);
}
@@ -410,31 +449,10 @@ void Stream::callback(int event, void* info, int toggle, int tries)
ALOGD("%s streamID %d wrong toggle", __func__, (int)mStreamID);
return;
}
- switch (event) {
- case AudioTrack::EVENT_MORE_DATA:
- ALOGW("%s streamID %d Invalid EVENT_MORE_DATA for static track",
- __func__, (int)mStreamID);
- break;
- case AudioTrack::EVENT_UNDERRUN:
- ALOGW("%s streamID %d Invalid EVENT_UNDERRUN for static track",
- __func__, (int)mStreamID);
- break;
- case AudioTrack::EVENT_BUFFER_END:
- ALOGV("%s streamID %d EVENT_BUFFER_END", __func__, (int)mStreamID);
- if (mState != IDLE) {
- activeStreamIDToRestart = mStreamID;
- mStopTimeNs = systemTime();
- }
- break;
- case AudioTrack::EVENT_LOOP_END:
- ALOGV("%s streamID %d EVENT_LOOP_END", __func__, (int)mStreamID);
- break;
- case AudioTrack::EVENT_NEW_IAUDIOTRACK:
- ALOGV("%s streamID %d NEW_IAUDIOTRACK", __func__, (int)mStreamID);
- break;
- default:
- ALOGW("%s streamID %d Invalid event %d", __func__, (int)mStreamID, event);
- break;
+ ALOGV("%s streamID %d EVENT_BUFFER_END", __func__, (int)mStreamID);
+ if (mState != IDLE) {
+ activeStreamIDToRestart = mStreamID;
+ mStopTimeNs = systemTime();
}
} // lock ends here. This is on the callback thread, no need to be precise.
if (activeStreamIDToRestart > 0) {
diff --git a/media/jni/soundpool/Stream.h b/media/jni/soundpool/Stream.h
index aa0eef5bc66e..0054eeca529a 100644
--- a/media/jni/soundpool/Stream.h
+++ b/media/jni/soundpool/Stream.h
@@ -124,6 +124,35 @@ public:
// This never changes. See top of header.
Stream* getPairStream() const;
+ // Stream ID of ourselves, or the pair depending on who holds the AudioTrack
+ int getCorrespondingStreamID();
+
+protected:
+ // AudioTrack callback interface implementation
+ class StreamCallback : public AudioTrack::IAudioTrackCallback {
+ public:
+ StreamCallback(Stream * stream, bool toggle) : mStream(stream), mToggle(toggle) {}
+ size_t onMoreData(const AudioTrack::Buffer& buffer) override;
+ void onUnderrun() override;
+ void onLoopEnd(int32_t loopsRemaining) override;
+ void onMarker(uint32_t markerPosition) override;
+ void onNewPos(uint32_t newPos) override;
+ void onBufferEnd() override;
+ void onNewIAudioTrack() override;
+ void onStreamEnd() override;
+ size_t onCanWriteMoreData(const AudioTrack::Buffer& buffer) override;
+
+ // Holding a raw ptr is technically unsafe, but, Stream objects persist
+ // through the lifetime of the StreamManager through the use of a
+ // unique_ptr<Stream[]>. Ensuring lifetime will cause us to give up
+ // locality as well as pay RefBase/sp performance cost, which we are
+ // unwilling to do. Non-owning refs to unique_ptrs are idiomatically raw
+ // ptrs, as below.
+ Stream * const mStream;
+ const bool mToggle;
+ };
+
+ sp<StreamCallback> mCallback;
private:
// garbage is used to release tracks and data outside of any lock.
void play_l(const std::shared_ptr<Sound>& sound, int streamID,
@@ -133,9 +162,7 @@ private:
void setVolume_l(float leftVolume, float rightVolume) REQUIRES(mLock);
// For use with AudioTrack callback.
- static void staticCallback(int event, void* user, void* info);
- void callback(int event, void* info, int toggle, int tries)
- NO_THREAD_SAFETY_ANALYSIS; // uses unique_lock
+ void onBufferEnd(int toggle, int tries) NO_THREAD_SAFETY_ANALYSIS;
// StreamManager should be set on construction and not changed.
// release mLock before calling into StreamManager
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java
deleted file mode 100644
index f23794b50543..000000000000
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2021 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.mediaframeworktest.unit;
-
-import static org.junit.Assert.assertEquals;
-
-import android.bluetooth.BluetoothProfile;
-import android.media.BluetoothProfileConnectionInfo;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class BluetoothProfileConnectionInfoTest {
-
- @Test
- public void testCoverageA2dp() {
- final boolean supprNoisy = false;
- final int volume = 42;
- final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
- .createA2dpInfo(supprNoisy, volume);
- assertEquals(info.getProfile(), BluetoothProfile.A2DP);
- assertEquals(info.isSuppressNoisyIntent(), supprNoisy);
- assertEquals(info.getVolume(), volume);
- }
-
- @Test
- public void testCoverageA2dpSink() {
- final int volume = 42;
- final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
- .createA2dpSinkInfo(volume);
- assertEquals(info.getProfile(), BluetoothProfile.A2DP_SINK);
- assertEquals(info.getVolume(), volume);
- }
-
- @Test
- public void testCoveragehearingAid() {
- final boolean supprNoisy = true;
- final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
- .createHearingAidInfo(supprNoisy);
- assertEquals(info.getProfile(), BluetoothProfile.HEARING_AID);
- assertEquals(info.isSuppressNoisyIntent(), supprNoisy);
- }
-
- @Test
- public void testCoverageLeAudio() {
- final boolean supprNoisy = false;
- final boolean isLeOutput = true;
- final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
- .createLeAudioInfo(supprNoisy, isLeOutput);
- assertEquals(info.getProfile(), BluetoothProfile.LE_AUDIO);
- assertEquals(info.isSuppressNoisyIntent(), supprNoisy);
- assertEquals(info.isLeOutput(), isLeOutput);
- }
-}
-
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 0c360519ceb2..65428de95519 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -48,6 +48,7 @@ private:
static APerformanceHintManager* create(sp<IHintManager> iHintManager);
sp<IHintManager> mHintManager;
+ const sp<IBinder> mToken = sp<BBinder>::make();
const int64_t mPreferredRateNanos;
};
@@ -119,11 +120,10 @@ APerformanceHintManager* APerformanceHintManager::create(sp<IHintManager> manage
APerformanceHintSession* APerformanceHintManager::createSession(
const int32_t* threadIds, size_t size, int64_t initialTargetWorkDurationNanos) {
- sp<IBinder> token = sp<BBinder>::make();
std::vector<int32_t> tids(threadIds, threadIds + size);
sp<IHintSession> session;
binder::Status ret =
- mHintManager->createHintSession(token, tids, initialTargetWorkDurationNanos, &session);
+ mHintManager->createHintSession(mToken, tids, initialTargetWorkDurationNanos, &session);
if (!ret.isOk() || !session) {
return nullptr;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java
index 61b8911acea4..0cb2c0b22a4c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java
@@ -21,7 +21,8 @@ import android.content.ClipData;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
-import android.database.Cursor;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
@@ -30,15 +31,15 @@ import android.graphics.Paint;
import android.graphics.RectF;
import android.media.ExifInterface;
import android.net.Uri;
-import android.os.AsyncTask;
import android.os.StrictMode;
-import android.provider.ContactsContract;
import android.provider.MediaStore;
import android.util.EventLog;
import android.util.Log;
import androidx.core.content.FileProvider;
+import com.android.settingslib.utils.ThreadUtils;
+
import libcore.io.Streams;
import java.io.File;
@@ -47,39 +48,64 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.concurrent.ExecutionException;
class AvatarPhotoController {
+
+ interface AvatarUi {
+ boolean isFinishing();
+
+ void returnUriResult(Uri uri);
+
+ void startActivityForResult(Intent intent, int resultCode);
+
+ boolean startSystemActivityForResult(Intent intent, int resultCode);
+
+ int getPhotoSize();
+ }
+
+ interface ContextInjector {
+ File getCacheDir();
+
+ Uri createTempImageUri(File parentDir, String fileName, boolean purge);
+
+ ContentResolver getContentResolver();
+ }
+
private static final String TAG = "AvatarPhotoController";
- private static final int REQUEST_CODE_CHOOSE_PHOTO = 1001;
- private static final int REQUEST_CODE_TAKE_PHOTO = 1002;
- private static final int REQUEST_CODE_CROP_PHOTO = 1003;
- // in rare cases we get a null Cursor when querying for DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI
- // so we need a default photo size
- private static final int DEFAULT_PHOTO_SIZE = 500;
+ static final int REQUEST_CODE_CHOOSE_PHOTO = 1001;
+ static final int REQUEST_CODE_TAKE_PHOTO = 1002;
+ static final int REQUEST_CODE_CROP_PHOTO = 1003;
private static final String IMAGES_DIR = "multi_user";
+ private static final String PRE_CROP_PICTURE_FILE_NAME = "PreCropEditUserPhoto.jpg";
private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg";
private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto.jpg";
private final int mPhotoSize;
- private final AvatarPickerActivity mActivity;
- private final String mFileAuthority;
+ private final AvatarUi mAvatarUi;
+ private final ContextInjector mContextInjector;
private final File mImagesDir;
+ private final Uri mPreCropPictureUri;
private final Uri mCropPictureUri;
private final Uri mTakePictureUri;
- AvatarPhotoController(AvatarPickerActivity activity, boolean waiting, String fileAuthority) {
- mActivity = activity;
- mFileAuthority = fileAuthority;
+ AvatarPhotoController(AvatarUi avatarUi, ContextInjector contextInjector, boolean waiting) {
+ mAvatarUi = avatarUi;
+ mContextInjector = contextInjector;
- mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR);
+ mImagesDir = new File(mContextInjector.getCacheDir(), IMAGES_DIR);
mImagesDir.mkdir();
- mCropPictureUri = createTempImageUri(activity, CROP_PICTURE_FILE_NAME, !waiting);
- mTakePictureUri = createTempImageUri(activity, TAKE_PICTURE_FILE_NAME, !waiting);
- mPhotoSize = getPhotoSize(activity);
+ mPreCropPictureUri = mContextInjector
+ .createTempImageUri(mImagesDir, PRE_CROP_PICTURE_FILE_NAME, !waiting);
+ mCropPictureUri =
+ mContextInjector.createTempImageUri(mImagesDir, CROP_PICTURE_FILE_NAME, !waiting);
+ mTakePictureUri =
+ mContextInjector.createTempImageUri(mImagesDir, TAKE_PICTURE_FILE_NAME, !waiting);
+ mPhotoSize = mAvatarUi.getPhotoSize();
}
/**
@@ -102,16 +128,12 @@ class AvatarPhotoController {
switch (requestCode) {
case REQUEST_CODE_CROP_PHOTO:
- mActivity.returnUriResult(pictureUri);
+ mAvatarUi.returnUriResult(pictureUri);
return true;
case REQUEST_CODE_TAKE_PHOTO:
case REQUEST_CODE_CHOOSE_PHOTO:
if (mTakePictureUri.equals(pictureUri)) {
- if (PhotoCapabilityUtils.canCropPhoto(mActivity)) {
- cropPhoto();
- } else {
- onPhotoNotCropped(pictureUri);
- }
+ cropPhoto(pictureUri);
} else {
copyAndCropPhoto(pictureUri);
}
@@ -123,55 +145,52 @@ class AvatarPhotoController {
void takePhoto() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE_SECURE);
appendOutputExtra(intent, mTakePictureUri);
- mActivity.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
+ mAvatarUi.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
}
void choosePhoto() {
Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES, null);
intent.setType("image/*");
- mActivity.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
+ mAvatarUi.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
}
private void copyAndCropPhoto(final Uri pictureUri) {
- // TODO: Replace AsyncTask
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- final ContentResolver cr = mActivity.getContentResolver();
+ try {
+ ThreadUtils.postOnBackgroundThread(() -> {
+ final ContentResolver cr = mContextInjector.getContentResolver();
try (InputStream in = cr.openInputStream(pictureUri);
- OutputStream out = cr.openOutputStream(mTakePictureUri)) {
+ OutputStream out = cr.openOutputStream(mPreCropPictureUri)) {
Streams.copy(in, out);
} catch (IOException e) {
Log.w(TAG, "Failed to copy photo", e);
+ return;
}
- return null;
- }
-
- @Override
- protected void onPostExecute(Void result) {
- if (!mActivity.isFinishing() && !mActivity.isDestroyed()) {
- cropPhoto();
- }
- }
- }.execute();
+ ThreadUtils.postOnMainThread(() -> {
+ if (!mAvatarUi.isFinishing()) {
+ cropPhoto(mPreCropPictureUri);
+ }
+ });
+ }).get();
+ } catch (InterruptedException | ExecutionException e) {
+ Log.e(TAG, "Error performing copy-and-crop", e);
+ }
}
- private void cropPhoto() {
+ private void cropPhoto(final Uri pictureUri) {
// TODO: Use a public intent, when there is one.
Intent intent = new Intent("com.android.camera.action.CROP");
- intent.setDataAndType(mTakePictureUri, "image/*");
+ intent.setDataAndType(pictureUri, "image/*");
appendOutputExtra(intent, mCropPictureUri);
appendCropExtras(intent);
- if (intent.resolveActivity(mActivity.getPackageManager()) != null) {
- try {
- StrictMode.disableDeathOnFileUriExposure();
- mActivity.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
- } finally {
- StrictMode.enableDeathOnFileUriExposure();
+ try {
+ StrictMode.disableDeathOnFileUriExposure();
+ if (mAvatarUi.startSystemActivityForResult(intent, REQUEST_CODE_CROP_PHOTO)) {
+ return;
}
- } else {
- onPhotoNotCropped(mTakePictureUri);
+ } finally {
+ StrictMode.enableDeathOnFileUriExposure();
}
+ onPhotoNotCropped(pictureUri);
}
private void appendOutputExtra(Intent intent, Uri pictureUri) {
@@ -192,24 +211,22 @@ class AvatarPhotoController {
}
private void onPhotoNotCropped(final Uri data) {
- // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change
- new AsyncTask<Void, Void, Bitmap>() {
- @Override
- protected Bitmap doInBackground(Void... params) {
+ try {
+ ThreadUtils.postOnBackgroundThread(() -> {
// Scale and crop to a square aspect ratio
Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize,
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(croppedImage);
Bitmap fullImage;
try {
- InputStream imageStream = mActivity.getContentResolver()
+ InputStream imageStream = mContextInjector.getContentResolver()
.openInputStream(data);
fullImage = BitmapFactory.decodeStream(imageStream);
} catch (FileNotFoundException fe) {
- return null;
+ return;
}
if (fullImage != null) {
- int rotation = getRotation(mActivity, data);
+ int rotation = getRotation(data);
final int squareSize = Math.min(fullImage.getWidth(),
fullImage.getHeight());
final int left = (fullImage.getWidth() - squareSize) / 2;
@@ -222,29 +239,27 @@ class AvatarPhotoController {
matrix.setRectToRect(rectSource, rectDest, Matrix.ScaleToFit.CENTER);
matrix.postRotate(rotation, mPhotoSize / 2f, mPhotoSize / 2f);
canvas.drawBitmap(fullImage, matrix, new Paint());
- return croppedImage;
- } else {
- // Bah! Got nothin.
- return null;
- }
- }
+ saveBitmapToFile(croppedImage, new File(mImagesDir, CROP_PICTURE_FILE_NAME));
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- saveBitmapToFile(bitmap, new File(mImagesDir, CROP_PICTURE_FILE_NAME));
- mActivity.returnUriResult(mCropPictureUri);
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+ ThreadUtils.postOnMainThread(() -> {
+ mAvatarUi.returnUriResult(mCropPictureUri);
+ });
+ }
+ }).get();
+ } catch (InterruptedException | ExecutionException e) {
+ Log.e(TAG, "Error performing internal crop", e);
+ }
}
/**
* Reads the image's exif data and determines the rotation degree needed to display the image
* in portrait mode.
*/
- private int getRotation(Context context, Uri selectedImage) {
+ private int getRotation(Uri selectedImage) {
int rotation = -1;
try {
- InputStream imageStream = context.getContentResolver().openInputStream(selectedImage);
+ InputStream imageStream =
+ mContextInjector.getContentResolver().openInputStream(selectedImage);
ExifInterface exif = new ExifInterface(imageStream);
rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);
} catch (IOException exception) {
@@ -274,24 +289,74 @@ class AvatarPhotoController {
}
}
- private static int getPhotoSize(Context context) {
- try (Cursor cursor = context.getContentResolver().query(
- ContactsContract.DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
- new String[]{ContactsContract.DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null)) {
- if (cursor != null) {
- cursor.moveToFirst();
- return cursor.getInt(0);
- } else {
- return DEFAULT_PHOTO_SIZE;
+ static class AvatarUiImpl implements AvatarUi {
+ private final AvatarPickerActivity mActivity;
+
+ AvatarUiImpl(AvatarPickerActivity activity) {
+ mActivity = activity;
+ }
+
+ @Override
+ public boolean isFinishing() {
+ return mActivity.isFinishing() || mActivity.isDestroyed();
+ }
+
+ @Override
+ public void returnUriResult(Uri uri) {
+ mActivity.returnUriResult(uri);
+ }
+
+ @Override
+ public void startActivityForResult(Intent intent, int resultCode) {
+ mActivity.startActivityForResult(intent, resultCode);
+ }
+
+ @Override
+ public boolean startSystemActivityForResult(Intent intent, int code) {
+ ActivityInfo info = intent.resolveActivityInfo(mActivity.getPackageManager(),
+ PackageManager.MATCH_SYSTEM_ONLY);
+ if (info == null) {
+ Log.w(TAG, "No system package activity could be found for code " + code);
+ return false;
}
+ intent.setPackage(info.packageName);
+ mActivity.startActivityForResult(intent, code);
+ return true;
+ }
+
+ @Override
+ public int getPhotoSize() {
+ return mActivity.getResources()
+ .getDimensionPixelSize(com.android.internal.R.dimen.user_icon_size);
}
}
- private Uri createTempImageUri(Context context, String fileName, boolean purge) {
- final File fullPath = new File(mImagesDir, fileName);
- if (purge) {
- fullPath.delete();
+ static class ContextInjectorImpl implements ContextInjector {
+ private final Context mContext;
+ private final String mFileAuthority;
+
+ ContextInjectorImpl(Context context, String fileAuthority) {
+ mContext = context;
+ mFileAuthority = fileAuthority;
+ }
+
+ @Override
+ public File getCacheDir() {
+ return mContext.getCacheDir();
+ }
+
+ @Override
+ public Uri createTempImageUri(File parentDir, String fileName, boolean purge) {
+ final File fullPath = new File(parentDir, fileName);
+ if (purge) {
+ fullPath.delete();
+ }
+ return FileProvider.getUriForFile(mContext, mFileAuthority, fullPath);
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mContext.getContentResolver();
}
- return FileProvider.getUriForFile(context, mFileAuthority, fullPath);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
index 1e1dfae9f7ac..75bb70a123c3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
@@ -95,7 +95,9 @@ public class AvatarPickerActivity extends Activity {
restoreState(savedInstanceState);
mAvatarPhotoController = new AvatarPhotoController(
- this, mWaitingForActivityResult, getFileAuthority());
+ new AvatarPhotoController.AvatarUiImpl(this),
+ new AvatarPhotoController.ContextInjectorImpl(this, getFileAuthority()),
+ mWaitingForActivityResult);
}
private void setUpButtons() {
diff --git a/packages/SettingsLib/tests/integ/AndroidManifest.xml b/packages/SettingsLib/tests/integ/AndroidManifest.xml
index da808dd54141..2a4dfdd84c63 100644
--- a/packages/SettingsLib/tests/integ/AndroidManifest.xml
+++ b/packages/SettingsLib/tests/integ/AndroidManifest.xml
@@ -25,10 +25,19 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-
<application>
<uses-library android:name="android.test.runner" />
<activity android:name=".drawer.SettingsDrawerActivityTest$TestActivity"/>
+
+ <provider
+ android:name="androidx.core.content.FileProvider"
+ android:authorities="com.android.settingslib.test"
+ android:grantUriPermissions="true"
+ android:exported="false">
+ <meta-data
+ android:name="android.support.FILE_PROVIDER_PATHS"
+ android:resource="@xml/file_paths" />
+ </provider>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/packages/SettingsLib/tests/integ/res/xml/file_paths.xml b/packages/SettingsLib/tests/integ/res/xml/file_paths.xml
new file mode 100644
index 000000000000..ccd11a46e429
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/res/xml/file_paths.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ 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.
+ -->
+
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Offer access to files under Context.getCacheDir() -->
+ <cache-path name="my_cache" />
+</paths>
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java
new file mode 100644
index 000000000000..9ebdba300266
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java
@@ -0,0 +1,299 @@
+/*
+ * 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.settingslib.users;
+
+import static com.android.settingslib.users.AvatarPhotoController.REQUEST_CODE_CHOOSE_PHOTO;
+import static com.android.settingslib.users.AvatarPhotoController.REQUEST_CODE_CROP_PHOTO;
+import static com.android.settingslib.users.AvatarPhotoController.REQUEST_CODE_TAKE_PHOTO;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.provider.MediaStore;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+@RunWith(AndroidJUnit4.class)
+public class AvatarPhotoControllerTest {
+
+ private static final long TIMEOUT_MILLIS = 5000;
+ private static final int PHOTO_SIZE = 200;
+
+ @Mock AvatarPhotoController.AvatarUi mMockAvatarUi;
+
+ private File mImagesDir;
+ private AvatarPhotoController mController;
+ private Uri mTakePhotoUri = Uri.parse(
+ "content://com.android.settingslib.test/my_cache/multi_user/TakeEditUserPhoto.jpg");
+ private Uri mCropPhotoUri = Uri.parse(
+ "content://com.android.settingslib.test/my_cache/multi_user/CropEditUserPhoto.jpg");
+ private Context mContext = InstrumentationRegistry.getTargetContext();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockAvatarUi.getPhotoSize()).thenReturn(PHOTO_SIZE);
+ when(mMockAvatarUi.startSystemActivityForResult(any(), anyInt())).thenReturn(true);
+
+ mImagesDir = new File(
+ InstrumentationRegistry.getTargetContext().getCacheDir(), "multi_user");
+ mImagesDir.mkdir();
+
+ AvatarPhotoController.ContextInjector contextInjector =
+ new AvatarPhotoController.ContextInjectorImpl(
+ InstrumentationRegistry.getTargetContext(), "com.android.settingslib.test");
+ mController = new AvatarPhotoController(mMockAvatarUi, contextInjector, false);
+ }
+
+ @After
+ public void tearDown() {
+ mImagesDir.delete();
+ }
+
+ @Test
+ public void takePhotoHasCorrectIntentAndResultCode() {
+ mController.takePhoto();
+
+ verifyStartActivityForResult(
+ MediaStore.ACTION_IMAGE_CAPTURE_SECURE, REQUEST_CODE_TAKE_PHOTO);
+ }
+
+ @Test
+ public void choosePhotoHasCorrectIntentAndResultCode() {
+ mController.choosePhoto();
+
+ verifyStartActivityForResult(
+ MediaStore.ACTION_PICK_IMAGES, REQUEST_CODE_CHOOSE_PHOTO);
+ }
+
+ @Test
+ public void takePhotoIsFollowedByCrop() throws IOException {
+ new File(mImagesDir, "file.txt").createNewFile();
+
+ Intent intent = new Intent();
+ intent.setData(Uri.parse(
+ "content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
+ mController.onActivityResult(
+ REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
+
+ verifyStartSystemActivityForResult(
+ "com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
+ }
+
+ @Test
+ public void takePhotoIsNotFollowedByCropWhenResultCodeNotOk() throws IOException {
+ new File(mImagesDir, "file.txt").createNewFile();
+
+ Intent intent = new Intent();
+ intent.setData(Uri.parse(
+ "content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
+ mController.onActivityResult(
+ REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_CANCELED, intent);
+
+ verify(mMockAvatarUi, never()).startActivityForResult(any(), anyInt());
+ verify(mMockAvatarUi, never()).startSystemActivityForResult(any(), anyInt());
+ }
+
+ @Test
+ public void takePhotoIsFollowedByCropWhenTakePhotoUriReturned() throws IOException {
+ new File(mImagesDir, "TakeEditUserPhoto.jpg").createNewFile();
+
+ Intent intent = new Intent();
+ intent.setData(mTakePhotoUri);
+ mController.onActivityResult(
+ REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
+
+ verifyStartSystemActivityForResult(
+ "com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
+ }
+
+ @Test
+ public void takePhotoIsNotFollowedByCropIntentWhenCropNotSupported() throws IOException {
+ when(mMockAvatarUi.startSystemActivityForResult(any(), anyInt())).thenReturn(false);
+
+ File file = new File(mImagesDir, "file.txt");
+ saveBitmapToFile(file);
+
+ Intent intent = new Intent();
+ intent.setData(Uri.parse(
+ "content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
+ mController.onActivityResult(
+ REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
+
+ verify(mMockAvatarUi, never()).startActivityForResult(any(), anyInt());
+ verify(mMockAvatarUi, never()).startSystemActivityForResult(any(), anyInt());
+ }
+
+ @Test
+ public void choosePhotoIsFollowedByCrop() throws IOException {
+ new File(mImagesDir, "file.txt").createNewFile();
+
+ Intent intent = new Intent();
+ intent.setData(Uri.parse(
+ "content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
+ mController.onActivityResult(
+ REQUEST_CODE_CHOOSE_PHOTO, Activity.RESULT_OK, intent);
+
+ verifyStartSystemActivityForResult(
+ "com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
+ }
+
+ @Test
+ public void choosePhotoIsNotFollowedByCropWhenResultCodeNotOk() throws IOException {
+ new File(mImagesDir, "file.txt").createNewFile();
+
+ Intent intent = new Intent();
+ intent.setData(Uri.parse(
+ "content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
+ mController.onActivityResult(
+ REQUEST_CODE_CHOOSE_PHOTO, Activity.RESULT_CANCELED, intent);
+
+ verify(mMockAvatarUi, never()).startActivityForResult(any(), anyInt());
+ verify(mMockAvatarUi, never()).startSystemActivityForResult(any(), anyInt());
+ }
+
+ @Test
+ public void choosePhotoIsFollowedByCropWhenTakePhotoUriReturned() throws IOException {
+ new File(mImagesDir, "TakeEditUserPhoto.jpg").createNewFile();
+
+ Intent intent = new Intent();
+ intent.setData(mTakePhotoUri);
+ mController.onActivityResult(
+ REQUEST_CODE_CHOOSE_PHOTO, Activity.RESULT_OK, intent);
+
+ verifyStartSystemActivityForResult(
+ "com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
+ }
+
+ @Test
+ public void cropPhotoResultIsReturnedIfResultOkAndContent() {
+ Intent intent = new Intent();
+ intent.setData(mCropPhotoUri);
+ mController.onActivityResult(REQUEST_CODE_CROP_PHOTO, Activity.RESULT_OK, intent);
+ verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS)).returnUriResult(mCropPhotoUri);
+ }
+
+ @Test
+ public void cropPhotoResultIsNotReturnedIfResultCancel() {
+ Intent intent = new Intent();
+ intent.setData(mCropPhotoUri);
+ mController.onActivityResult(REQUEST_CODE_CROP_PHOTO, Activity.RESULT_CANCELED, intent);
+ verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS).times(0)).returnUriResult(mCropPhotoUri);
+ }
+
+ @Test
+ public void cropPhotoResultIsNotReturnedIfResultNotContent() {
+ Intent intent = new Intent();
+ intent.setData(Uri.parse("file://test"));
+ mController.onActivityResult(REQUEST_CODE_CROP_PHOTO, Activity.RESULT_OK, intent);
+ verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS).times(0)).returnUriResult(mCropPhotoUri);
+ }
+
+ @Test
+ public void cropDoesNotUseTakePhotoUri() throws IOException {
+ new File(mImagesDir, "file.txt").createNewFile();
+
+ Intent intent = new Intent();
+ intent.setData(Uri.parse(
+ "content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
+ mController.onActivityResult(
+ REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
+
+ Intent startIntent = verifyStartSystemActivityForResult(
+ "com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
+ assertThat(startIntent.getData()).isNotEqualTo(mTakePhotoUri);
+ }
+
+ @Test
+ public void internalCropUsedIfNoSystemCropperFound() throws IOException {
+ when(mMockAvatarUi.startSystemActivityForResult(any(), anyInt())).thenReturn(false);
+
+ new File(mImagesDir, "file.txt").createNewFile();
+
+ Intent intent = new Intent();
+ intent.setData(Uri.parse(
+ "content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
+ mController.onActivityResult(
+ REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
+
+ Intent startIntent = verifyStartSystemActivityForResult(
+ "com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
+ assertThat(startIntent.getData()).isNotEqualTo(mTakePhotoUri);
+
+ verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS)).returnUriResult(mCropPhotoUri);
+
+ InputStream imageStream = mContext.getContentResolver().openInputStream(mCropPhotoUri);
+ Bitmap bitmap = BitmapFactory.decodeStream(imageStream);
+ assertThat(bitmap.getWidth()).isEqualTo(PHOTO_SIZE);
+ assertThat(bitmap.getHeight()).isEqualTo(PHOTO_SIZE);
+ }
+
+ private Intent verifyStartActivityForResult(String action, int resultCode) {
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS))
+ .startActivityForResult(captor.capture(), eq(resultCode));
+ Intent intent = captor.getValue();
+ assertThat(intent.getAction()).isEqualTo(action);
+ return intent;
+ }
+
+ private Intent verifyStartSystemActivityForResult(String action, int resultCode) {
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS))
+ .startSystemActivityForResult(captor.capture(), eq(resultCode));
+ Intent intent = captor.getValue();
+ assertThat(intent.getAction()).isEqualTo(action);
+ return intent;
+ }
+
+ private void saveBitmapToFile(File file) throws IOException {
+ Bitmap bitmap = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888);
+ OutputStream os = new FileOutputStream(file);
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
+ os.flush();
+ os.close();
+ }
+
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 42aa2053e1a4..fa3360cd31f6 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -231,6 +231,7 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.SKIP_DIRECTION, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SILENCE_GESTURE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, JSON_OBJECT_VALIDATOR);
+ VALIDATORS.put(Secure.NAV_BAR_FORCE_VISIBLE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.NAV_BAR_KIDS_MODE, BOOLEAN_VALIDATOR);
VALIDATORS.put(
Secure.NAVIGATION_MODE, new DiscreteValueValidator(new String[] {"0", "1", "2"}));
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index a2b699288868..1c5bf81faf3e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2253,8 +2253,11 @@ class SettingsProtoDumpUtil {
SecureSettingsProto.MULTI_PRESS_TIMEOUT);
dumpSetting(s, p,
+ Settings.Secure.NAV_BAR_FORCE_VISIBLE,
+ SecureSettingsProto.NavBar.NAV_BAR_FORCE_VISIBLE);
+ dumpSetting(s, p,
Settings.Secure.NAV_BAR_KIDS_MODE,
- SecureSettingsProto.NAV_BAR_KIDS_MODE);
+ SecureSettingsProto.NavBar.NAV_BAR_KIDS_MODE);
dumpSetting(s, p,
Settings.Secure.NAVIGATION_MODE,
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
index 0ae5dc745478..5084ca48e608 100644
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
+++ b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
@@ -1,254 +1 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!-- Copyright (C) 2021 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.
--->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt">
- <aapt:attr name="android:drawable">
- <vector android:height="60dp" android:width="60dp" android:viewportHeight="60"
- android:viewportWidth="60">
- <group android:name="_R_G">
- <group android:name="_R_G_L_1_G" android:translateX="-0.05000000000000071">
- <group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30"
- android:translateY="38.75" android:scaleX="1" android:scaleY="1">
- <path android:name="_R_G_L_1_G_D_0_P_0"
- android:fillColor="@color/biometric_dialog_error"
- android:fillAlpha="1" android:fillType="nonZero"
- android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/>
- </group>
- <group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30"
- android:translateY="25" android:pivotX="0.002" android:pivotY="7.488"
- android:scaleX="1" android:scaleY="1">
- <path android:name="_R_G_L_1_G_D_1_P_0"
- android:fillColor="@color/biometric_dialog_error"
- android:fillAlpha="1" android:fillType="nonZero"
- android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/>
- </group>
- <path android:name="_R_G_L_1_G_D_2_P_0"
- android:strokeColor="@color/biometric_dialog_error"
- android:strokeLineCap="round" android:strokeLineJoin="round"
- android:strokeWidth="2.5" android:strokeAlpha="1"
- android:trimPathStart="0" android:trimPathEnd="1"
- android:trimPathOffset="0"
- android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "/>
- </group>
- <group android:name="_R_G_L_0_G" android:translateX="-10.325"
- android:translateY="-10.25">
- <path android:name="_R_G_L_0_G_D_0_P_0"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round" android:strokeLineJoin="round"
- android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
- android:trimPathEnd="0" android:trimPathOffset="0"
- android:pathData=" M31.41 48.43 C30.78,46.69 30.78,44.91 30.78,44.91 C30.78,40.09 34.88,36.16 40.32,36.16 C45.77,36.16 49.87,40.09 49.87,44.91 C49.87,44.91 49.87,45.17 49.87,45.17 C49.87,46.97 48.41,48.43 46.61,48.43 C45.28,48.43 44.09,47.63 43.6,46.39 C43.6,46.39 42.51,43.66 42.51,43.66 C42.02,42.42 40.82,41.61 39.49,41.61 C37.69,41.61 36.23,43.07 36.23,44.87 C36.23,47.12 37.26,49.26 39.02,50.67 C39.02,50.67 39.64,51.16 39.64,51.16 "/>
- <path android:name="_R_G_L_0_G_D_1_P_0"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round" android:strokeLineJoin="round"
- android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
- android:trimPathEnd="0" android:trimPathOffset="0"
- android:pathData=" M32.14 27.3 C34.5,26 37.31,25.25 40.33,25.25 C43.34,25.25 46.15,26 48.51,27.3 "/>
- <path android:name="_R_G_L_0_G_D_2_P_0"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round" android:strokeLineJoin="round"
- android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
- android:trimPathEnd="0" android:trimPathOffset="0"
- android:pathData=" M29.42 36.16 C31.35,32.94 35.51,30.71 40.33,30.71 C45.14,30.71 49.3,32.94 51.23,36.16 "/>
- <path android:name="_R_G_L_0_G_D_3_P_0"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round" android:strokeLineJoin="round"
- android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
- android:trimPathEnd="0" android:trimPathOffset="0"
- android:pathData=" M47.14 52.52 C45.33,54.21 42.94,55.25 40.33,55.25 C37.71,55.25 35.32,54.21 33.51,52.52 "/>
- </group>
- </group>
- <group android:name="time_group"/>
- </vector>
- </aapt:attr>
- <target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="scaleX" android:duration="67"
- android:startOffset="0" android:valueFrom="1" android:valueTo="1.1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="67"
- android:startOffset="0" android:valueFrom="1" android:valueTo="1.1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="100"
- android:startOffset="67" android:valueFrom="1.1" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="100"
- android:startOffset="67" android:valueFrom="1.1" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="scaleX" android:duration="67"
- android:startOffset="0" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="67"
- android:startOffset="0" android:valueFrom="1" android:valueTo="1.1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="100"
- android:startOffset="67" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="100"
- android:startOffset="67" android:valueFrom="1.1" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_D_2_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="trimPathEnd" android:duration="67"
- android:startOffset="0" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="trimPathEnd" android:duration="133"
- android:startOffset="67" android:valueFrom="1" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
- android:startOffset="0" android:valueFrom="0" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
- android:startOffset="83" android:valueFrom="0" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_1_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
- android:startOffset="0" android:valueFrom="0" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
- android:startOffset="83" android:valueFrom="0" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_2_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
- android:startOffset="0" android:valueFrom="0" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
- android:startOffset="83" android:valueFrom="0" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_3_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
- android:startOffset="0" android:valueFrom="0" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
- android:startOffset="83" android:valueFrom="0" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="time_group">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="translateX" android:duration="417"
- android:startOffset="0" android:valueFrom="0" android:valueTo="1"
- android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
-</animated-vector>
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0.975"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" android:translateY="38.75" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/></group><group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_1_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/></group></group><group android:name="_R_G_L_0_G" android:translateX="20.659" android:translateY="15.75"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="167" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="167" android:startOffset="0" android:valueFrom="2.5" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="83" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0.975" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_2_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="417" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml
new file mode 100644
index 000000000000..c4f818146011
--- /dev/null
+++ b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml
@@ -0,0 +1 @@
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0.975"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" android:translateY="38.75" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/></group><group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_1_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/></group></group><group android:name="_R_G_L_0_G" android:translateX="20.75" android:translateY="15.75" android:pivotX="19.341" android:pivotY="24.25" android:scaleX="0.5" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="167" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="167" android:startOffset="0" android:valueFrom="2.5" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="83" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0.975" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="233" android:startOffset="0" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="267" android:startOffset="233" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M37.91 20.05 C37.91,20.05 37.89,14.16 37.89,14.16 C37.89,10.79 35.15,8.05 31.86,8.03 C28.46,8.01 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="167" android:startOffset="0" android:valueFrom="0.5" android:valueTo="0.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="167" android:startOffset="0" android:valueFrom="0.5" android:valueTo="0.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="333" android:startOffset="167" android:valueFrom="0.5" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="333" android:startOffset="167" android:valueFrom="0.5" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0" android:valueTo="0.5" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="683" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
index fc2c7d00f3a7..c05a8d55c16c 100644
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
+++ b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
@@ -1,247 +1 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!-- Copyright (C) 2021 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.
--->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt">
- <aapt:attr name="android:drawable">
- <vector android:height="60dp" android:width="60dp" android:viewportHeight="60"
- android:viewportWidth="60">
- <group android:name="_R_G">
- <group android:name="_R_G_L_1_G" android:translateX="-0.05000000000000071">
- <group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30"
- android:translateY="38.75" android:scaleX="0" android:scaleY="0">
- <path android:name="_R_G_L_1_G_D_0_P_0"
- android:fillColor="@color/biometric_dialog_error"
- android:fillAlpha="1" android:fillType="nonZero"
- android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/>
- </group>
- <group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30"
- android:translateY="25" android:pivotX="0.002" android:pivotY="7.488"
- android:scaleX="1" android:scaleY="0">
- <path android:name="_R_G_L_1_G_D_1_P_0"
- android:fillColor="@color/biometric_dialog_error"
- android:fillAlpha="1" android:fillType="nonZero"
- android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/>
- </group>
- <path android:name="_R_G_L_1_G_D_2_P_0"
- android:strokeColor="@color/biometric_dialog_error"
- android:strokeLineCap="round" android:strokeLineJoin="round"
- android:strokeWidth="2.5" android:strokeAlpha="1"
- android:trimPathStart="1" android:trimPathEnd="1"
- android:trimPathOffset="0"
- android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "/>
- </group>
- <group android:name="_R_G_L_0_G" android:translateX="-10.325"
- android:translateY="-10.25">
- <path android:name="_R_G_L_0_G_D_0_P_0"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round" android:strokeLineJoin="round"
- android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
- android:trimPathEnd="1" android:trimPathOffset="0"
- android:pathData=" M31.41 48.43 C30.78,46.69 30.78,44.91 30.78,44.91 C30.78,40.09 34.88,36.16 40.32,36.16 C45.77,36.16 49.87,40.09 49.87,44.91 C49.87,44.91 49.87,45.17 49.87,45.17 C49.87,46.97 48.41,48.43 46.61,48.43 C45.28,48.43 44.09,47.63 43.6,46.39 C43.6,46.39 42.51,43.66 42.51,43.66 C42.02,42.42 40.82,41.61 39.49,41.61 C37.69,41.61 36.23,43.07 36.23,44.87 C36.23,47.12 37.26,49.26 39.02,50.67 C39.02,50.67 39.64,51.16 39.64,51.16 "/>
- <path android:name="_R_G_L_0_G_D_1_P_0"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round" android:strokeLineJoin="round"
- android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
- android:trimPathEnd="1" android:trimPathOffset="0"
- android:pathData=" M32.14 27.3 C34.5,26 37.31,25.25 40.33,25.25 C43.34,25.25 46.15,26 48.51,27.3 "/>
- <path android:name="_R_G_L_0_G_D_2_P_0"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round" android:strokeLineJoin="round"
- android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
- android:trimPathEnd="1" android:trimPathOffset="0"
- android:pathData=" M29.42 36.16 C31.35,32.94 35.51,30.71 40.33,30.71 C45.14,30.71 49.3,32.94 51.23,36.16 "/>
- <path android:name="_R_G_L_0_G_D_3_P_0"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round" android:strokeLineJoin="round"
- android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
- android:trimPathEnd="1" android:trimPathOffset="0"
- android:pathData=" M47.14 52.52 C45.33,54.21 42.94,55.25 40.33,55.25 C37.71,55.25 35.32,54.21 33.51,52.52 "/>
- </group>
- </group>
- <group android:name="time_group"/>
- </vector>
- </aapt:attr>
- <target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="scaleX" android:duration="167"
- android:startOffset="0" android:valueFrom="0" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="167"
- android:startOffset="0" android:valueFrom="0" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="100"
- android:startOffset="167" android:valueFrom="0"
- android:valueTo="1.1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="100"
- android:startOffset="167" android:valueFrom="0"
- android:valueTo="1.1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="67"
- android:startOffset="267" android:valueFrom="1.1"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="67"
- android:startOffset="267" android:valueFrom="1.1"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="scaleX" android:duration="167"
- android:startOffset="0" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="167"
- android:startOffset="0" android:valueFrom="0" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="100"
- android:startOffset="167" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="100"
- android:startOffset="167" android:valueFrom="0"
- android:valueTo="1.1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="67"
- android:startOffset="267" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.341,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="67"
- android:startOffset="267" android:valueFrom="1.1"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_D_2_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="trimPathStart" android:duration="267"
- android:startOffset="0" android:valueFrom="1" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="trimPathStart" android:duration="167"
- android:startOffset="0" android:valueFrom="0" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_1_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="trimPathStart" android:duration="167"
- android:startOffset="0" android:valueFrom="0" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_2_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="trimPathStart" android:duration="167"
- android:startOffset="0" android:valueFrom="0" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_3_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="trimPathStart" android:duration="167"
- android:startOffset="0" android:valueFrom="0" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="time_group">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="translateX" android:duration="350"
- android:startOffset="0" android:valueFrom="0" android:valueTo="1"
- android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
-</animated-vector>
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0" android:strokeAlpha="0" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" android:translateY="38.75" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/></group><group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" android:scaleX="1" android:scaleY="0"><path android:name="_R_G_L_1_G_D_1_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/></group></group><group android:name="_R_G_L_0_G" android:translateX="20.659" android:translateY="15.75"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="83" android:startOffset="0" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeWidth" android:duration="233" android:startOffset="67" android:valueFrom="0" android:valueTo="2.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="167" android:valueFrom="0" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="167" android:valueFrom="0" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="267" android:valueFrom="1.1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="267" android:valueFrom="1.1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="167" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="167" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="167" android:valueFrom="0" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="267" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.341,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="267" android:valueFrom="1.1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_2_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="350" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml
new file mode 100644
index 000000000000..16944294a94e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml
@@ -0,0 +1 @@
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_2_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="20.75" android:translateY="15.75"><path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group><group android:name="_R_G_L_0_G" android:translateX="37.357" android:translateY="43.25" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1.41866" android:scaleY="1.41866"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#d3e3fd" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " android:valueTo="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="143" android:startOffset="107" android:valueFrom="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueTo="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.331,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="140" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="50" android:startOffset="140" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " android:valueTo="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="107" android:valueFrom="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueTo="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_2_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="250" android:startOffset="0" android:valueFrom="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " android:valueTo="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.189,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="95" android:startOffset="0" android:valueFrom="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " android:valueTo="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="24" android:startOffset="95" android:valueFrom="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueTo="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="81" android:startOffset="119" android:valueFrom="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.261,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="233" android:startOffset="200" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M37.91 20.05 C37.91,20.05 37.89,14.16 37.89,14.16 C37.89,10.79 35.15,8.05 31.86,8.03 C28.46,8.01 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.123,0 0.23,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="120" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="20" android:startOffset="120" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="517" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
index 89690e8ff0ec..58adb9146bd0 100644
--- a/packages/SystemUI/res/layout/auth_biometric_contents.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -49,8 +49,8 @@
<ImageView
android:id="@+id/biometric_icon"
- android:layout_width="@dimen/biometric_dialog_biometric_icon_size"
- android:layout_height="@dimen/biometric_dialog_biometric_icon_size"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:layout_gravity="center"
android:contentDescription="@null"
android:scaleType="fitXY" />
diff --git a/packages/SystemUI/res/layout/auth_biometric_face_to_fingerprint_view.xml b/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml
index 7cf1789961d3..05ca2a786e3a 100644
--- a/packages/SystemUI/res/layout/auth_biometric_face_to_fingerprint_view.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml
@@ -14,12 +14,13 @@
~ limitations under the License.
-->
-<com.android.systemui.biometrics.AuthBiometricFaceToFingerprintView
+<com.android.systemui.biometrics.AuthBiometricFingerprintAndFaceView
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/contents"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/auth_biometric_contents"/>
-</com.android.systemui.biometrics.AuthBiometricFaceToFingerprintView> \ No newline at end of file
+</com.android.systemui.biometrics.AuthBiometricFingerprintAndFaceView> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_biometric_udfps_view.xml b/packages/SystemUI/res/layout/auth_biometric_view.xml
index 238288eb9f69..ee4da25f2284 100644
--- a/packages/SystemUI/res/layout/auth_biometric_udfps_view.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_view.xml
@@ -1,5 +1,5 @@
<!--
- ~ Copyright (C) 2020 The Android Open Source Project
+ ~ 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.
@@ -14,7 +14,7 @@
~ limitations under the License.
-->
-<com.android.systemui.biometrics.AuthBiometricUdfpsView
+<com.android.systemui.biometrics.AuthBiometricView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/contents"
android:layout_width="match_parent"
@@ -23,4 +23,4 @@
<include layout="@layout/auth_biometric_contents"/>
-</com.android.systemui.biometrics.AuthBiometricUdfpsView> \ No newline at end of file
+</com.android.systemui.biometrics.AuthBiometricView> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 65c17b9028e1..5a7efca3dece 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -876,7 +876,8 @@
<dimen name="remote_input_history_extra_height">60dp</dimen>
<!-- Biometric Dialog values -->
- <dimen name="biometric_dialog_biometric_icon_size">64dp</dimen>
+ <dimen name="biometric_dialog_face_icon_size">64dp</dimen>
+ <dimen name="biometric_dialog_fingerprint_icon_size">80dp</dimen>
<dimen name="biometric_dialog_button_negative_max_width">160dp</dimen>
<dimen name="biometric_dialog_button_positive_max_width">136dp</dimen>
<dimen name="biometric_dialog_corner_size">4dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6d336e60e1d3..9e1f57bbfa75 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -322,6 +322,8 @@
<string name="biometric_dialog_face_icon_description_confirmed">Confirmed</string>
<!-- Message shown when a biometric is authenticated, waiting for the user to confirm authentication [CHAR LIMIT=40]-->
<string name="biometric_dialog_tap_confirm">Tap Confirm to complete</string>
+ <!-- Message shown when a biometric has authenticated with a user's face and is waiting for the user to confirm authentication [CHAR LIMIT=60]-->
+ <string name="biometric_dialog_tap_confirm_with_face">Unlocked by your face. Press to continue.</string>
<!-- Talkback string when a biometric is authenticated [CHAR LIMIT=NONE] -->
<string name="biometric_dialog_authenticated">Authenticated</string>
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index 885a1777f30b..aafbf7e4d439 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -172,6 +172,17 @@ public class WindowMagnification extends CoreStartable implements WindowMagnifie
}
@MainThread
+ void moveWindowMagnifierToPositionInternal(int displayId, float positionX, float positionY,
+ IRemoteMagnificationAnimationCallback callback) {
+ final WindowMagnificationController windowMagnificationController =
+ mMagnificationControllerSupplier.get(displayId);
+ if (windowMagnificationController != null) {
+ windowMagnificationController.moveWindowMagnifierToPosition(positionX, positionY,
+ callback);
+ }
+ }
+
+ @MainThread
void disableWindowMagnification(int displayId,
@Nullable IRemoteMagnificationAnimationCallback callback) {
final WindowMagnificationController windowMagnificationController =
@@ -210,9 +221,9 @@ public class WindowMagnification extends CoreStartable implements WindowMagnifie
}
@Override
- public void onDrag(int displayId) {
+ public void onMove(int displayId) {
if (mWindowMagnificationConnectionImpl != null) {
- mWindowMagnificationConnectionImpl.onDrag(displayId);
+ mWindowMagnificationConnectionImpl.onMove(displayId);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
index dc1e0054ff24..3b4114bfb984 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
@@ -156,6 +156,7 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp
}
mAnimationCallback = animationCallback;
setupEnableAnimationSpecs(scale, centerX, centerY);
+
if (mEndSpec.equals(mStartSpec)) {
if (mState == STATE_DISABLED) {
mController.enableWindowMagnificationInternal(scale, centerX, centerY,
@@ -178,6 +179,24 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp
}
}
+ void moveWindowMagnifierToPosition(float centerX, float centerY,
+ IRemoteMagnificationAnimationCallback callback) {
+ if (mState == STATE_ENABLED) {
+ // We set the animation duration to shortAnimTime which would be reset at the end.
+ mValueAnimator.setDuration(mContext.getResources()
+ .getInteger(com.android.internal.R.integer.config_shortAnimTime));
+ enableWindowMagnification(Float.NaN, centerX, centerY,
+ /* magnificationFrameOffsetRatioX */ Float.NaN,
+ /* magnificationFrameOffsetRatioY */ Float.NaN, callback);
+ } else if (mState == STATE_ENABLING) {
+ sendAnimationCallback(false);
+ mAnimationCallback = callback;
+ mValueAnimator.setDuration(mContext.getResources()
+ .getInteger(com.android.internal.R.integer.config_shortAnimTime));
+ setupEnableAnimationSpecs(Float.NaN, centerX, centerY);
+ }
+ }
+
private void setupEnableAnimationSpecs(float scale, float centerX, float centerY) {
if (mController == null) {
return;
@@ -193,9 +212,16 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp
R.integer.magnification_default_scale) : scale, centerX, centerY);
} else {
mStartSpec.set(currentScale, currentCenterX, currentCenterY);
- mEndSpec.set(Float.isNaN(scale) ? currentScale : scale,
- Float.isNaN(centerX) ? currentCenterX : centerX,
- Float.isNaN(centerY) ? currentCenterY : centerY);
+
+ final float endScale = (mState == STATE_ENABLING ? mEndSpec.mScale : currentScale);
+ final float endCenterX =
+ (mState == STATE_ENABLING ? mEndSpec.mCenterX : currentCenterX);
+ final float endCenterY =
+ (mState == STATE_ENABLING ? mEndSpec.mCenterY : currentCenterY);
+
+ mEndSpec.set(Float.isNaN(scale) ? endScale : scale,
+ Float.isNaN(centerX) ? endCenterX : centerX,
+ Float.isNaN(centerY) ? endCenterY : centerY);
}
if (DEBUG) {
Log.d(TAG, "SetupEnableAnimationSpecs : mStartSpec = " + mStartSpec + ", endSpec = "
@@ -269,6 +295,9 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp
setState(STATE_ENABLED);
}
sendAnimationCallback(true);
+ // We reset the duration to config_longAnimTime
+ mValueAnimator.setDuration(mContext.getResources()
+ .getInteger(com.android.internal.R.integer.config_longAnimTime));
}
@Override
@@ -313,10 +342,10 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp
mMagnificationFrameOffsetRatioX, mMagnificationFrameOffsetRatioY);
}
- private static ValueAnimator newValueAnimator(Resources resources) {
+ private static ValueAnimator newValueAnimator(Resources resource) {
final ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setDuration(
- resources.getInteger(com.android.internal.R.integer.config_longAnimTime));
+ resource.getInteger(com.android.internal.R.integer.config_longAnimTime));
valueAnimator.setInterpolator(new AccelerateInterpolator(2.5f));
valueAnimator.setFloatValues(0.0f, 1.0f);
return valueAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
index 1d22633455e9..aa684faee5ab 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
@@ -77,6 +77,13 @@ class WindowMagnificationConnectionImpl extends IWindowMagnificationConnection.S
}
@Override
+ public void moveWindowMagnifierToPosition(int displayId, float positionX, float positionY,
+ IRemoteMagnificationAnimationCallback callback) {
+ mHandler.post(() -> mWindowMagnification.moveWindowMagnifierToPositionInternal(
+ displayId, positionX, positionY, callback));
+ }
+
+ @Override
public void showMagnificationButton(int displayId, int magnificationMode) {
mHandler.post(
() -> mModeSwitchesController.showButton(displayId, magnificationMode));
@@ -143,10 +150,10 @@ class WindowMagnificationConnectionImpl extends IWindowMagnificationConnection.S
}
}
- void onDrag(int displayId) {
+ void onMove(int displayId) {
if (mConnectionCallback != null) {
try {
- mConnectionCallback.onDrag(displayId);
+ mConnectionCallback.onMove(displayId);
} catch (RemoteException e) {
Log.e(TAG, "Failed to inform taking control by a user", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index de03993a6f17..50ca447090b5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -852,6 +852,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
@Override
public void move(int xOffset, int yOffset) {
moveWindowMagnifier(xOffset, yOffset);
+ mWindowMagnifierCallback.onMove(mDisplayId);
}
/**
@@ -985,6 +986,14 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
}
+ void moveWindowMagnifierToPosition(float positionX, float positionY,
+ IRemoteMagnificationAnimationCallback callback) {
+ if (mMirrorSurfaceView == null) {
+ return;
+ }
+ mAnimationController.moveWindowMagnifierToPosition(positionX, positionY, callback);
+ }
+
/**
* Gets the scale.
*
@@ -1037,8 +1046,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
@Override
public boolean onDrag(float offsetX, float offsetY) {
- moveWindowMagnifier(offsetX, offsetY);
- mWindowMagnifierCallback.onDrag(mDisplayId);
+ move((int) offsetX, (int) offsetY);
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
index bdded10dfa1d..c334ca664c46 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
@@ -17,7 +17,6 @@
package com.android.systemui.accessibility;
import android.graphics.Rect;
-import android.view.ViewConfiguration;
/**
* A callback to inform {@link com.android.server.accessibility.AccessibilityManagerService} about
@@ -56,11 +55,9 @@ interface WindowMagnifierCallback {
void onAccessibilityActionPerformed(int displayId);
/**
- * Called when the user is performing dragging gesture. It is started after the offset
- * between the down location and the move event location exceed
- * {@link ViewConfiguration#getScaledTouchSlop()}.
+ * Called when the user is performing a move action.
*
* @param displayId The logical display id.
*/
- void onDrag(int displayId);
+ void onMove(int displayId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
new file mode 100644
index 000000000000..55611f7d7ada
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.biometrics
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.util.Log
+import android.widget.ImageView
+import com.android.systemui.R
+import com.android.systemui.biometrics.AuthBiometricView.BiometricState
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING_ANIMATING_IN
+import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
+import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
+import com.android.systemui.biometrics.AuthBiometricView.STATE_IDLE
+import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
+
+private const val TAG = "AuthBiometricFaceIconController"
+
+/** Face only icon animator for BiometricPrompt. */
+class AuthBiometricFaceIconController(
+ context: Context,
+ iconView: ImageView
+) : AuthIconController(context, iconView) {
+
+ // false = dark to light, true = light to dark
+ private var lastPulseLightToDark = false
+
+ @BiometricState
+ private var state = 0
+
+ init {
+ val size = context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
+ iconView.layoutParams.width = size
+ iconView.layoutParams.height = size
+ showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
+ }
+
+ private fun startPulsing() {
+ lastPulseLightToDark = false
+ animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true)
+ }
+
+ private fun pulseInNextDirection() {
+ val iconRes = if (lastPulseLightToDark) {
+ R.drawable.face_dialog_pulse_dark_to_light
+ } else {
+ R.drawable.face_dialog_pulse_light_to_dark
+ }
+ animateIcon(iconRes, true /* repeat */)
+ lastPulseLightToDark = !lastPulseLightToDark
+ }
+
+ override fun handleAnimationEnd(drawable: Drawable) {
+ if (state == STATE_AUTHENTICATING || state == STATE_HELP) {
+ pulseInNextDirection()
+ }
+ }
+
+ override fun updateIcon(@BiometricState oldState: Int, @BiometricState newState: Int) {
+ val lastStateIsErrorIcon = (oldState == STATE_ERROR || oldState == STATE_HELP)
+ if (newState == STATE_AUTHENTICATING_ANIMATING_IN) {
+ showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
+ iconView.contentDescription = context.getString(
+ R.string.biometric_dialog_face_icon_description_authenticating
+ )
+ } else if (newState == STATE_AUTHENTICATING) {
+ startPulsing()
+ iconView.contentDescription = context.getString(
+ R.string.biometric_dialog_face_icon_description_authenticating
+ )
+ } else if (oldState == STATE_PENDING_CONFIRMATION && newState == STATE_AUTHENTICATED) {
+ animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
+ iconView.contentDescription = context.getString(
+ R.string.biometric_dialog_face_icon_description_confirmed
+ )
+ } else if (lastStateIsErrorIcon && newState == STATE_IDLE) {
+ animateIconOnce(R.drawable.face_dialog_error_to_idle)
+ iconView.contentDescription = context.getString(
+ R.string.biometric_dialog_face_icon_description_idle
+ )
+ } else if (lastStateIsErrorIcon && newState == STATE_AUTHENTICATED) {
+ animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
+ iconView.contentDescription = context.getString(
+ R.string.biometric_dialog_face_icon_description_authenticated
+ )
+ } else if (newState == STATE_ERROR && oldState != STATE_ERROR) {
+ animateIconOnce(R.drawable.face_dialog_dark_to_error)
+ } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
+ animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
+ iconView.contentDescription = context.getString(
+ R.string.biometric_dialog_face_icon_description_authenticated
+ )
+ } else if (newState == STATE_PENDING_CONFIRMATION) {
+ animateIconOnce(R.drawable.face_dialog_wink_from_dark)
+ iconView.contentDescription = context.getString(
+ R.string.biometric_dialog_face_icon_description_authenticated
+ )
+ } else if (newState == STATE_IDLE) {
+ showStaticDrawable(R.drawable.face_dialog_idle_static)
+ iconView.contentDescription = context.getString(
+ R.string.biometric_dialog_face_icon_description_idle
+ )
+ } else {
+ Log.w(TAG, "Unhandled state: $newState")
+ }
+ state = newState
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java
deleted file mode 100644
index ae3e94b9a1cb..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright (C) 2021 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 static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
-
-import android.content.Context;
-import android.hardware.biometrics.BiometricAuthenticator.Modality;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
-
-/**
- * Manages the layout of an auth dialog for devices with both a face sensor and a fingerprint
- * sensor. Face authentication is attempted first, followed by fingerprint if the initial attempt is
- * unsuccessful.
- */
-public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView {
- private static final String TAG = "BiometricPrompt/AuthBiometricFaceToFingerprintView";
-
- protected static class UdfpsIconController extends IconController {
- @BiometricState private int mIconState = STATE_IDLE;
-
- protected UdfpsIconController(
- @NonNull Context context, @NonNull ImageView iconView, @NonNull TextView textView) {
- super(context, iconView, textView);
- }
-
- void updateState(@BiometricState int newState) {
- updateState(mIconState, newState);
- }
-
- @Override
- protected void updateState(int lastState, int newState) {
- final boolean lastStateIsErrorIcon =
- lastState == STATE_ERROR || lastState == STATE_HELP;
-
- switch (newState) {
- case STATE_IDLE:
- case STATE_AUTHENTICATING_ANIMATING_IN:
- case STATE_AUTHENTICATING:
- case STATE_PENDING_CONFIRMATION:
- case STATE_AUTHENTICATED:
- if (lastStateIsErrorIcon) {
- animateOnce(R.drawable.fingerprint_dialog_error_to_fp);
- } else {
- showStaticDrawable(R.drawable.fingerprint_dialog_fp_to_error);
- }
- mIconView.setContentDescription(mContext.getString(
- R.string.accessibility_fingerprint_dialog_fingerprint_icon));
- break;
-
- case STATE_ERROR:
- case STATE_HELP:
- if (!lastStateIsErrorIcon) {
- animateOnce(R.drawable.fingerprint_dialog_fp_to_error);
- } else {
- showStaticDrawable(R.drawable.fingerprint_dialog_error_to_fp);
- }
- mIconView.setContentDescription(mContext.getString(
- R.string.biometric_dialog_try_again));
- break;
-
- default:
- Log.e(TAG, "Unknown biometric dialog state: " + newState);
- break;
- }
-
- mState = newState;
- mIconState = newState;
- }
- }
-
- @Modality private int mActiveSensorType = TYPE_FACE;
- @Nullable private ModalityListener mModalityListener;
- @Nullable private FingerprintSensorPropertiesInternal mFingerprintSensorProps;
- @Nullable private UdfpsDialogMeasureAdapter mUdfpsMeasureAdapter;
- @Nullable @VisibleForTesting UdfpsIconController mUdfpsIconController;
-
-
- public AuthBiometricFaceToFingerprintView(Context context) {
- super(context);
- }
-
- public AuthBiometricFaceToFingerprintView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @VisibleForTesting
- AuthBiometricFaceToFingerprintView(Context context, AttributeSet attrs, Injector injector) {
- super(context, attrs, injector);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mUdfpsIconController = new UdfpsIconController(mContext, mIconView, mIndicatorView);
- }
-
- @Modality
- int getActiveSensorType() {
- return mActiveSensorType;
- }
-
- boolean isFingerprintUdfps() {
- return mFingerprintSensorProps.isAnyUdfpsType();
- }
-
- void setModalityListener(@NonNull ModalityListener listener) {
- mModalityListener = listener;
- }
-
- void setFingerprintSensorProps(@NonNull FingerprintSensorPropertiesInternal sensorProps) {
- mFingerprintSensorProps = sensorProps;
- }
-
- @Override
- protected int getDelayAfterAuthenticatedDurationMs() {
- return mActiveSensorType == TYPE_FINGERPRINT ? 0
- : super.getDelayAfterAuthenticatedDurationMs();
- }
-
- @Override
- protected boolean supportsManualRetry() {
- return false;
- }
-
- @Override
- public void onAuthenticationFailed(
- @Modality int modality, @Nullable String failureReason) {
- super.onAuthenticationFailed(modality, checkErrorForFallback(failureReason));
- }
-
- @Override
- public void onError(int modality, String error) {
- super.onError(modality, checkErrorForFallback(error));
- }
-
- private String checkErrorForFallback(String message) {
- if (mActiveSensorType == TYPE_FACE) {
- Log.d(TAG, "Falling back to fingerprint: " + message);
-
- // switching from face -> fingerprint mode, suppress root error messages
- mCallback.onAction(Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR);
- return mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead);
- }
- return message;
- }
-
- @Override
- @BiometricState
- protected int getStateForAfterError() {
- if (mActiveSensorType == TYPE_FACE) {
- return STATE_AUTHENTICATING;
- }
-
- return super.getStateForAfterError();
- }
-
- @Override
- public void updateState(@BiometricState int newState) {
- if (mActiveSensorType == TYPE_FACE) {
- if (newState == STATE_HELP || newState == STATE_ERROR) {
- mActiveSensorType = TYPE_FINGERPRINT;
-
- setRequireConfirmation(false);
- mConfirmButton.setEnabled(false);
- mConfirmButton.setVisibility(View.GONE);
-
- if (mModalityListener != null) {
- mModalityListener.onModalitySwitched(TYPE_FACE, mActiveSensorType);
- }
-
- // Deactivate the face icon controller so it stops drawing to the view
- mFaceIconController.deactivate();
- // Then, activate this icon controller. We need to start in the "idle" state
- mUdfpsIconController.updateState(STATE_IDLE);
- }
- } else { // Fingerprint
- mUdfpsIconController.updateState(newState);
- }
-
- super.updateState(newState);
- }
-
- @Override
- @NonNull
- AuthDialog.LayoutParams onMeasureInternal(int width, int height) {
- final AuthDialog.LayoutParams layoutParams = super.onMeasureInternal(width, height);
- return isFingerprintUdfps()
- ? getUdfpsMeasureAdapter().onMeasureInternal(width, height, layoutParams)
- : layoutParams;
- }
-
- @NonNull
- private UdfpsDialogMeasureAdapter getUdfpsMeasureAdapter() {
- if (mUdfpsMeasureAdapter == null
- || mUdfpsMeasureAdapter.getSensorProps() != mFingerprintSensorProps) {
- mUdfpsMeasureAdapter = new UdfpsDialogMeasureAdapter(this, mFingerprintSensorProps);
- }
- return mUdfpsMeasureAdapter;
- }
-
- @Override
- public void onSaveState(@NonNull Bundle outState) {
- super.onSaveState(outState);
- outState.putInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE, mActiveSensorType);
- outState.putParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS, mFingerprintSensorProps);
- }
-
- @Override
- public void restoreState(@Nullable Bundle savedState) {
- super.restoreState(savedState);
- if (savedState != null) {
- mActiveSensorType = savedState.getInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE, TYPE_FACE);
- mFingerprintSensorProps =
- savedState.getParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
deleted file mode 100644
index 48f6431aec69..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import android.content.Context;
-import android.graphics.drawable.Animatable2;
-import android.graphics.drawable.AnimatedVectorDrawable;
-import android.graphics.drawable.Drawable;
-import android.hardware.biometrics.BiometricAuthenticator.Modality;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
-
-public class AuthBiometricFaceView extends AuthBiometricView {
-
- private static final String TAG = "BiometricPrompt/AuthBiometricFaceView";
-
- // Delay before dismissing after being authenticated/confirmed.
- private static final int HIDE_DELAY_MS = 500;
-
- protected static class IconController extends Animatable2.AnimationCallback {
- protected Context mContext;
- protected ImageView mIconView;
- protected TextView mTextView;
- protected Handler mHandler;
- protected boolean mLastPulseLightToDark; // false = dark to light, true = light to dark
- protected @BiometricState int mState;
- protected boolean mDeactivated;
-
- protected IconController(Context context, ImageView iconView, TextView textView) {
- mContext = context;
- mIconView = iconView;
- mTextView = textView;
- mHandler = new Handler(Looper.getMainLooper());
- showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light);
- }
-
- protected void animateOnce(int iconRes) {
- animateIcon(iconRes, false);
- }
-
- protected void showStaticDrawable(int iconRes) {
- mIconView.setImageDrawable(mContext.getDrawable(iconRes));
- }
-
- protected void animateIcon(int iconRes, boolean repeat) {
- Log.d(TAG, "animateIcon, state: " + mState + ", deactivated: " + mDeactivated);
- if (mDeactivated) {
- return;
- }
-
- final AnimatedVectorDrawable icon =
- (AnimatedVectorDrawable) mContext.getDrawable(iconRes);
- mIconView.setImageDrawable(icon);
- icon.forceAnimationOnUI();
- if (repeat) {
- icon.registerAnimationCallback(this);
- }
- icon.start();
- }
-
- protected void startPulsing() {
- mLastPulseLightToDark = false;
- animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true);
- }
-
- protected void pulseInNextDirection() {
- int iconRes = mLastPulseLightToDark ? R.drawable.face_dialog_pulse_dark_to_light
- : R.drawable.face_dialog_pulse_light_to_dark;
- animateIcon(iconRes, true /* repeat */);
- mLastPulseLightToDark = !mLastPulseLightToDark;
- }
-
- @Override
- public void onAnimationEnd(Drawable drawable) {
- super.onAnimationEnd(drawable);
- Log.d(TAG, "onAnimationEnd, mState: " + mState + ", deactivated: " + mDeactivated);
- if (mDeactivated) {
- return;
- }
-
- if (mState == STATE_AUTHENTICATING || mState == STATE_HELP) {
- pulseInNextDirection();
- }
- }
-
- protected void deactivate() {
- mDeactivated = true;
- }
-
- protected void updateState(int lastState, int newState) {
- if (mDeactivated) {
- Log.w(TAG, "Ignoring updateState when deactivated: " + newState);
- return;
- }
-
- final boolean lastStateIsErrorIcon =
- lastState == STATE_ERROR || lastState == STATE_HELP;
-
- if (newState == STATE_AUTHENTICATING_ANIMATING_IN) {
- showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light);
- mIconView.setContentDescription(mContext.getString(
- R.string.biometric_dialog_face_icon_description_authenticating));
- } else if (newState == STATE_AUTHENTICATING) {
- startPulsing();
- mIconView.setContentDescription(mContext.getString(
- R.string.biometric_dialog_face_icon_description_authenticating));
- } else if (lastState == STATE_PENDING_CONFIRMATION && newState == STATE_AUTHENTICATED) {
- animateOnce(R.drawable.face_dialog_dark_to_checkmark);
- mIconView.setContentDescription(mContext.getString(
- R.string.biometric_dialog_face_icon_description_confirmed));
- } else if (lastStateIsErrorIcon && newState == STATE_IDLE) {
- animateOnce(R.drawable.face_dialog_error_to_idle);
- mIconView.setContentDescription(mContext.getString(
- R.string.biometric_dialog_face_icon_description_idle));
- } else if (lastStateIsErrorIcon && newState == STATE_AUTHENTICATED) {
- animateOnce(R.drawable.face_dialog_dark_to_checkmark);
- mIconView.setContentDescription(mContext.getString(
- R.string.biometric_dialog_face_icon_description_authenticated));
- } else if (newState == STATE_ERROR && lastState != STATE_ERROR) {
- animateOnce(R.drawable.face_dialog_dark_to_error);
- } else if (lastState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
- animateOnce(R.drawable.face_dialog_dark_to_checkmark);
- mIconView.setContentDescription(mContext.getString(
- R.string.biometric_dialog_face_icon_description_authenticated));
- } else if (newState == STATE_PENDING_CONFIRMATION) {
- animateOnce(R.drawable.face_dialog_wink_from_dark);
- mIconView.setContentDescription(mContext.getString(
- R.string.biometric_dialog_face_icon_description_authenticated));
- } else if (newState == STATE_IDLE) {
- showStaticDrawable(R.drawable.face_dialog_idle_static);
- mIconView.setContentDescription(mContext.getString(
- R.string.biometric_dialog_face_icon_description_idle));
- } else {
- Log.w(TAG, "Unhandled state: " + newState);
- }
- mState = newState;
- }
- }
-
- @Nullable @VisibleForTesting IconController mFaceIconController;
- @NonNull private final OnAttachStateChangeListener mOnAttachStateChangeListener =
- new OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
-
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- mFaceIconController.deactivate();
- }
- };
-
- public AuthBiometricFaceView(Context context) {
- this(context, null);
- }
-
- public AuthBiometricFaceView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @VisibleForTesting
- AuthBiometricFaceView(Context context, AttributeSet attrs, Injector injector) {
- super(context, attrs, injector);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mFaceIconController = new IconController(mContext, mIconView, mIndicatorView);
-
- addOnAttachStateChangeListener(mOnAttachStateChangeListener);
- }
-
- @Override
- protected int getDelayAfterAuthenticatedDurationMs() {
- return HIDE_DELAY_MS;
- }
-
- @Override
- protected int getStateForAfterError() {
- return STATE_IDLE;
- }
-
- @Override
- protected void handleResetAfterError() {
- resetErrorView();
- }
-
- @Override
- protected void handleResetAfterHelp() {
- resetErrorView();
- }
-
- @Override
- protected boolean supportsSmallDialog() {
- return true;
- }
-
- @Override
- protected boolean supportsManualRetry() {
- return true;
- }
-
- @Override
- public void updateState(@BiometricState int newState) {
- mFaceIconController.updateState(mState, newState);
-
- if (newState == STATE_AUTHENTICATING_ANIMATING_IN ||
- (newState == STATE_AUTHENTICATING && getSize() == AuthDialog.SIZE_MEDIUM)) {
- resetErrorView();
- }
-
- // Do this last since the state variable gets updated.
- super.updateState(newState);
- }
-
- @Override
- public void onAuthenticationFailed(@Modality int modality, @Nullable String failureReason) {
- if (getSize() == AuthDialog.SIZE_MEDIUM) {
- if (supportsManualRetry()) {
- mTryAgainButton.setVisibility(View.VISIBLE);
- mConfirmButton.setVisibility(View.GONE);
- }
- }
-
- // Do this last since we want to know if the button is being animated (in the case of
- // small -> medium dialog)
- super.onAuthenticationFailed(modality, failureReason);
- }
-
- private void resetErrorView() {
- mIndicatorView.setTextColor(mTextColorHint);
- mIndicatorView.setVisibility(View.INVISIBLE);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.kt
new file mode 100644
index 000000000000..be89d10393dd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.biometrics
+
+import android.content.Context
+import android.hardware.biometrics.BiometricAuthenticator.Modality
+import android.util.AttributeSet
+
+/** Face only view for BiometricPrompt. */
+class AuthBiometricFaceView(
+ context: Context,
+ attrs: AttributeSet? = null
+) : AuthBiometricView(context, attrs) {
+
+ override fun getDelayAfterAuthenticatedDurationMs() = HIDE_DELAY_MS
+
+ override fun getStateForAfterError() = STATE_IDLE
+
+ override fun handleResetAfterError() = resetErrorView()
+
+ override fun handleResetAfterHelp() = resetErrorView()
+
+ override fun supportsSmallDialog() = true
+
+ override fun supportsManualRetry() = true
+
+ override fun supportsRequireConfirmation() = true
+
+ override fun createIconController(): AuthIconController =
+ AuthBiometricFaceIconController(mContext, mIconView)
+
+ override fun updateState(@BiometricState newState: Int) {
+ if (newState == STATE_AUTHENTICATING_ANIMATING_IN ||
+ newState == STATE_AUTHENTICATING && size == AuthDialog.SIZE_MEDIUM) {
+ resetErrorView()
+ }
+
+ // Do this last since the state variable gets updated.
+ super.updateState(newState)
+ }
+
+ override fun onAuthenticationFailed(
+ @Modality modality: Int,
+ failureReason: String?
+ ) {
+ if (size == AuthDialog.SIZE_MEDIUM) {
+ if (supportsManualRetry()) {
+ mTryAgainButton.visibility = VISIBLE
+ mConfirmButton.visibility = GONE
+ }
+ }
+
+ // Do this last since we want to know if the button is being animated (in the case of
+ // small -> medium dialog)
+ super.onAuthenticationFailed(modality, failureReason)
+ }
+
+ private fun resetErrorView() {
+ mIndicatorView.setTextColor(mTextColorHint)
+ mIndicatorView.visibility = INVISIBLE
+ }
+
+ companion object {
+ /** Delay before dismissing after being authenticated/confirmed. */
+ const val HIDE_DELAY_MS = 500
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
new file mode 100644
index 000000000000..3e4e573c9531
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.biometrics
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.widget.ImageView
+import com.android.systemui.R
+import com.android.systemui.biometrics.AuthBiometricView.BiometricState
+import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
+import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
+import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
+
+/** Face/Fingerprint combined icon animator for BiometricPrompt. */
+class AuthBiometricFingerprintAndFaceIconController(
+ context: Context,
+ iconView: ImageView
+) : AuthBiometricFingerprintIconController(context, iconView) {
+
+ override val actsAsConfirmButton: Boolean = true
+
+ override fun shouldAnimateForTransition(
+ @BiometricState oldState: Int,
+ @BiometricState newState: Int
+ ): Boolean = when (newState) {
+ STATE_PENDING_CONFIRMATION -> true
+ STATE_AUTHENTICATED -> false
+ else -> super.shouldAnimateForTransition(oldState, newState)
+ }
+
+ override fun getAnimationForTransition(
+ @BiometricState oldState: Int,
+ @BiometricState newState: Int
+ ): Drawable? = when (newState) {
+ STATE_PENDING_CONFIRMATION -> {
+ if (oldState == STATE_ERROR || oldState == STATE_HELP) {
+ context.getDrawable(R.drawable.fingerprint_dialog_error_to_unlock)
+ } else {
+ context.getDrawable(R.drawable.fingerprint_dialog_fp_to_unlock)
+ }
+ }
+ STATE_AUTHENTICATED -> null
+ else -> super.getAnimationForTransition(oldState, newState)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt
new file mode 100644
index 000000000000..7371442bdd07
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.biometrics
+
+import android.content.Context
+import android.hardware.biometrics.BiometricAuthenticator.Modality
+import android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE
+import android.util.AttributeSet
+import com.android.systemui.R
+
+/** Face/Fingerprint combined view for BiometricPrompt. */
+class AuthBiometricFingerprintAndFaceView(
+ context: Context,
+ attrs: AttributeSet?
+) : AuthBiometricFingerprintView(context, attrs) {
+
+ constructor (context: Context) : this(context, null)
+
+ override fun getConfirmationPrompt() = R.string.biometric_dialog_tap_confirm_with_face
+
+ override fun forceRequireConfirmation(@Modality modality: Int) = modality == TYPE_FACE
+
+ override fun ignoreUnsuccessfulEventsFrom(@Modality modality: Int) = modality == TYPE_FACE
+
+ override fun onPointerDown(failedModalities: Set<Int>) = failedModalities.contains(TYPE_FACE)
+
+ override fun createIconController(): AuthIconController =
+ AuthBiometricFingerprintAndFaceIconController(mContext, mIconView)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
new file mode 100644
index 000000000000..cd16379cd5b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.biometrics
+
+import android.content.Context
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.Drawable
+import android.widget.ImageView
+import com.android.systemui.R
+import com.android.systemui.biometrics.AuthBiometricView.BiometricState
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING_ANIMATING_IN
+import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
+import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
+import com.android.systemui.biometrics.AuthBiometricView.STATE_IDLE
+import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
+
+/** Fingerprint only icon animator for BiometricPrompt. */
+open class AuthBiometricFingerprintIconController(
+ context: Context,
+ iconView: ImageView
+) : AuthIconController(context, iconView) {
+
+ init {
+ val size = context.resources.getDimensionPixelSize(
+ R.dimen.biometric_dialog_fingerprint_icon_size
+ )
+ iconView.layoutParams.width = size
+ iconView.layoutParams.height = size
+ }
+
+ override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) {
+ val icon = getAnimationForTransition(lastState, newState) ?: return
+
+ iconView.setImageDrawable(icon)
+
+ val iconContentDescription = getIconContentDescription(newState)
+ if (iconContentDescription != null) {
+ iconView.contentDescription = iconContentDescription
+ }
+
+ (icon as? AnimatedVectorDrawable)?.apply {
+ reset()
+ if (shouldAnimateForTransition(lastState, newState)) {
+ forceAnimationOnUI()
+ start()
+ }
+ }
+ }
+
+ private fun getIconContentDescription(@BiometricState newState: Int): CharSequence? {
+ val id = when (newState) {
+ STATE_IDLE,
+ STATE_AUTHENTICATING_ANIMATING_IN,
+ STATE_AUTHENTICATING,
+ STATE_PENDING_CONFIRMATION,
+ STATE_AUTHENTICATED -> R.string.accessibility_fingerprint_dialog_fingerprint_icon
+ STATE_ERROR,
+ STATE_HELP -> R.string.biometric_dialog_try_again
+ else -> null
+ }
+ return if (id != null) context.getString(id) else null
+ }
+
+ protected open fun shouldAnimateForTransition(
+ @BiometricState oldState: Int,
+ @BiometricState newState: Int
+ ) = when (newState) {
+ STATE_HELP,
+ STATE_ERROR -> true
+ STATE_AUTHENTICATING_ANIMATING_IN,
+ STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
+ STATE_AUTHENTICATED -> false
+ else -> false
+ }
+
+ protected open fun getAnimationForTransition(
+ @BiometricState oldState: Int,
+ @BiometricState newState: Int
+ ): Drawable? {
+ val id = when (newState) {
+ STATE_HELP,
+ STATE_ERROR -> R.drawable.fingerprint_dialog_fp_to_error
+ STATE_AUTHENTICATING_ANIMATING_IN,
+ STATE_AUTHENTICATING -> {
+ if (oldState == STATE_ERROR || oldState == STATE_HELP) {
+ R.drawable.fingerprint_dialog_error_to_fp
+ } else {
+ R.drawable.fingerprint_dialog_fp_to_error
+ }
+ }
+ STATE_AUTHENTICATED -> R.drawable.fingerprint_dialog_fp_to_error
+ else -> return null
+ }
+ return if (id != null) context.getDrawable(id) else null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java
deleted file mode 100644
index ee602bc9cb78..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-
-import android.content.Context;
-import android.graphics.drawable.AnimatedVectorDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-
-import com.android.systemui.R;
-
-public class AuthBiometricFingerprintView extends AuthBiometricView {
-
- private static final String TAG = "BiometricPrompt/AuthBiometricFingerprintView";
-
- public AuthBiometricFingerprintView(Context context) {
- this(context, null);
- }
-
- public AuthBiometricFingerprintView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected int getDelayAfterAuthenticatedDurationMs() {
- return 0;
- }
-
- @Override
- protected int getStateForAfterError() {
- return STATE_AUTHENTICATING;
- }
-
- @Override
- protected void handleResetAfterError() {
- showTouchSensorString();
- }
-
- @Override
- protected void handleResetAfterHelp() {
- showTouchSensorString();
- }
-
- @Override
- protected boolean supportsSmallDialog() {
- return false;
- }
-
- @Override
- public void updateState(@BiometricState int newState) {
- updateIcon(mState, newState);
-
- // Do this last since the state variable gets updated.
- super.updateState(newState);
- }
-
- @Override
- void onAttachedToWindowInternal() {
- super.onAttachedToWindowInternal();
- showTouchSensorString();
- }
-
- private void showTouchSensorString() {
- mIndicatorView.setText(R.string.fingerprint_dialog_touch_sensor);
- mIndicatorView.setTextColor(mTextColorHint);
- }
-
- private void updateIcon(int lastState, int newState) {
- final Drawable icon = getAnimationForTransition(lastState, newState);
- if (icon == null) {
- Log.e(TAG, "Animation not found, " + lastState + " -> " + newState);
- return;
- }
-
- final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable
- ? (AnimatedVectorDrawable) icon
- : null;
-
- mIconView.setImageDrawable(icon);
-
- final CharSequence iconContentDescription = getIconContentDescription(newState);
- if (iconContentDescription != null) {
- mIconView.setContentDescription(iconContentDescription);
- }
-
- if (animation != null && shouldAnimateForTransition(lastState, newState)) {
- animation.forceAnimationOnUI();
- animation.start();
- }
- }
-
- @Nullable
- private CharSequence getIconContentDescription(int newState) {
- switch (newState) {
- case STATE_IDLE:
- case STATE_AUTHENTICATING_ANIMATING_IN:
- case STATE_AUTHENTICATING:
- case STATE_PENDING_CONFIRMATION:
- case STATE_AUTHENTICATED:
- return mContext.getString(
- R.string.accessibility_fingerprint_dialog_fingerprint_icon);
-
- case STATE_ERROR:
- case STATE_HELP:
- return mContext.getString(R.string.biometric_dialog_try_again);
-
- default:
- return null;
- }
- }
-
- private boolean shouldAnimateForTransition(int oldState, int newState) {
- switch (newState) {
- case STATE_HELP:
- case STATE_ERROR:
- return true;
- case STATE_AUTHENTICATING_ANIMATING_IN:
- case STATE_AUTHENTICATING:
- if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- return true;
- } else {
- return false;
- }
- case STATE_AUTHENTICATED:
- return false;
- default:
- return false;
- }
- }
-
- private Drawable getAnimationForTransition(int oldState, int newState) {
- int iconRes;
-
- switch (newState) {
- case STATE_HELP:
- case STATE_ERROR:
- iconRes = R.drawable.fingerprint_dialog_fp_to_error;
- break;
- case STATE_AUTHENTICATING_ANIMATING_IN:
- case STATE_AUTHENTICATING:
- if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- iconRes = R.drawable.fingerprint_dialog_error_to_fp;
- } else {
- iconRes = R.drawable.fingerprint_dialog_fp_to_error;
- }
- break;
- case STATE_AUTHENTICATED:
- iconRes = R.drawable.fingerprint_dialog_fp_to_error;
- break;
- default:
- return null;
- }
-
- return mContext.getDrawable(iconRes);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
new file mode 100644
index 000000000000..368bc3aadb70
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.biometrics
+
+import android.content.Context
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.util.AttributeSet
+import android.util.Log
+import android.widget.FrameLayout
+import android.widget.TextView
+import com.android.systemui.R
+
+private const val TAG = "AuthBiometricFingerprintView"
+
+/** Fingerprint only view for BiometricPrompt. */
+open class AuthBiometricFingerprintView(
+ context: Context,
+ attrs: AttributeSet? = null
+) : AuthBiometricView(context, attrs) {
+ /** If this view is for a UDFPS sensor. */
+ var isUdfps = false
+ private set
+
+ private var udfpsAdapter: UdfpsDialogMeasureAdapter? = null
+
+ /** Set the [sensorProps] of this sensor so the view can be customized prior to layout. */
+ fun setSensorProperties(sensorProps: FingerprintSensorPropertiesInternal) {
+ isUdfps = sensorProps.isAnyUdfpsType
+ udfpsAdapter = if (isUdfps) UdfpsDialogMeasureAdapter(this, sensorProps) else null
+ }
+
+ override fun onMeasureInternal(width: Int, height: Int): AuthDialog.LayoutParams {
+ val layoutParams = super.onMeasureInternal(width, height)
+ return udfpsAdapter?.onMeasureInternal(width, height, layoutParams) ?: layoutParams
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+
+ val adapter = udfpsAdapter
+ if (adapter != null) {
+ // Move the UDFPS icon and indicator text if necessary. This probably only needs to happen
+ // for devices where the UDFPS sensor is too low.
+ // TODO(b/201510778): Update this logic to support cases where the sensor or text overlap
+ // the button bar area.
+ val bottomSpacerHeight = adapter.bottomSpacerHeight
+ Log.w(TAG, "bottomSpacerHeight: $bottomSpacerHeight")
+ if (bottomSpacerHeight < 0) {
+ val iconFrame = findViewById<FrameLayout>(R.id.biometric_icon_frame)!!
+ iconFrame.translationY = -bottomSpacerHeight.toFloat()
+ val indicator = findViewById<TextView>(R.id.indicator)!!
+ indicator.translationY = -bottomSpacerHeight.toFloat()
+ }
+ }
+ }
+
+ override fun getDelayAfterAuthenticatedDurationMs() = 0
+
+ override fun getStateForAfterError() = STATE_AUTHENTICATING
+
+ override fun handleResetAfterError() = showTouchSensorString()
+
+ override fun handleResetAfterHelp() = showTouchSensorString()
+
+ override fun supportsSmallDialog() = false
+
+ override fun createIconController(): AuthIconController =
+ AuthBiometricFingerprintIconController(mContext, mIconView)
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ showTouchSensorString()
+ }
+
+ private fun showTouchSensorString() {
+ mIndicatorView.setText(R.string.fingerprint_dialog_touch_sensor)
+ mIndicatorView.setTextColor(mTextColorHint)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
new file mode 100644
index 000000000000..ce5e600e6a77
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.biometrics
+
+import android.annotation.DrawableRes
+import android.content.Context
+import android.graphics.drawable.Animatable2
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.Drawable
+import android.util.Log
+import android.widget.ImageView
+import com.android.systemui.biometrics.AuthBiometricView.BiometricState
+
+private const val TAG = "AuthIconController"
+
+/** Controller for animating the BiometricPrompt icon/affordance. */
+abstract class AuthIconController(
+ protected val context: Context,
+ protected val iconView: ImageView
+) : Animatable2.AnimationCallback() {
+
+ /** If this controller should ignore events and pause. */
+ var deactivated: Boolean = false
+
+ /** If the icon view should be treated as an alternate "confirm" button. */
+ open val actsAsConfirmButton: Boolean = false
+
+ final override fun onAnimationStart(drawable: Drawable) {
+ super.onAnimationStart(drawable)
+ }
+
+ final override fun onAnimationEnd(drawable: Drawable) {
+ super.onAnimationEnd(drawable)
+
+ if (!deactivated) {
+ handleAnimationEnd(drawable)
+ }
+ }
+
+ /** Set the icon to a static image. */
+ protected fun showStaticDrawable(@DrawableRes iconRes: Int) {
+ iconView.setImageDrawable(context.getDrawable(iconRes))
+ }
+
+ /** Animate a resource. */
+ protected fun animateIconOnce(@DrawableRes iconRes: Int) {
+ animateIcon(iconRes, false)
+ }
+
+ /** Animate a resource. */
+ protected fun animateIcon(@DrawableRes iconRes: Int, repeat: Boolean) {
+ if (!deactivated) {
+ val icon = context.getDrawable(iconRes) as AnimatedVectorDrawable
+ iconView.setImageDrawable(icon)
+ icon.forceAnimationOnUI()
+ if (repeat) {
+ icon.registerAnimationCallback(this)
+ }
+ icon.start()
+ }
+ }
+
+ /** Update the icon to reflect the [newState]. */
+ fun updateState(@BiometricState lastState: Int, @BiometricState newState: Int) {
+ if (deactivated) {
+ Log.w(TAG, "Ignoring updateState when deactivated: $newState")
+ } else {
+ updateIcon(lastState, newState)
+ }
+ }
+
+ /** If the icon should act as a "retry" button in the [currentState]. */
+ fun iconTapSendsRetryWhen(@BiometricState currentState: Int): Boolean = false
+
+ /** Call during [updateState] if the controller is not [deactivated]. */
+ abstract fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int)
+
+ /** Called during [onAnimationEnd] if the controller is not [deactivated]. */
+ open fun handleAnimationEnd(drawable: Drawable) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java
deleted file mode 100644
index d80d9cc9d62d..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2020 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.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.widget.FrameLayout;
-import android.widget.TextView;
-
-import com.android.systemui.R;
-
-/**
- * Manages the layout for under-display fingerprint sensors (UDFPS). Ensures that UI elements
- * do not overlap with
- */
-public class AuthBiometricUdfpsView extends AuthBiometricFingerprintView {
- private static final String TAG = "AuthBiometricUdfpsView";
-
- @Nullable private UdfpsDialogMeasureAdapter mMeasureAdapter;
-
- public AuthBiometricUdfpsView(Context context) {
- this(context, null /* attrs */);
- }
-
- public AuthBiometricUdfpsView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- void setSensorProps(@NonNull FingerprintSensorPropertiesInternal sensorProps) {
- if (mMeasureAdapter == null || mMeasureAdapter.getSensorProps() != sensorProps) {
- mMeasureAdapter = new UdfpsDialogMeasureAdapter(this, sensorProps);
- }
- }
-
- @Override
- @NonNull
- AuthDialog.LayoutParams onMeasureInternal(int width, int height) {
- final AuthDialog.LayoutParams layoutParams = super.onMeasureInternal(width, height);
- return mMeasureAdapter != null
- ? mMeasureAdapter.onMeasureInternal(width, height, layoutParams)
- : layoutParams;
- }
-
- @Override
- void onLayoutInternal() {
- super.onLayoutInternal();
-
- // Move the UDFPS icon and indicator text if necessary. This probably only needs to happen
- // for devices where the UDFPS sensor is too low.
- // TODO(b/201510778): Update this logic to support cases where the sensor or text overlap
- // the button bar area.
- final int bottomSpacerHeight = mMeasureAdapter.getBottomSpacerHeight();
- Log.w(TAG, "bottomSpacerHeight: " + bottomSpacerHeight);
- if (bottomSpacerHeight < 0) {
- FrameLayout iconFrame = findViewById(R.id.biometric_icon_frame);
- iconFrame.setTranslationY(-bottomSpacerHeight);
-
- TextView indicator = findViewById(R.id.indicator);
- indicator.setTranslationY(-bottomSpacerHeight);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index 1496f170dffe..76d4aa839ef3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -25,6 +25,7 @@ import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StringRes;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricPrompt;
@@ -44,19 +45,21 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
/**
- * Contains the Biometric views (title, subtitle, icon, buttons, etc) and its controllers.
+ * Contains the Biometric views (title, subtitle, icon, buttons, etc.) and its controllers.
*/
-public abstract class AuthBiometricView extends LinearLayout {
+public class AuthBiometricView extends LinearLayout {
- private static final String TAG = "BiometricPrompt/AuthBiometricView";
+ private static final String TAG = "AuthBiometricView";
/**
* Authentication hardware idle.
@@ -102,13 +105,6 @@ public abstract class AuthBiometricView extends LinearLayout {
int ACTION_BUTTON_TRY_AGAIN = 4;
int ACTION_ERROR = 5;
int ACTION_USE_DEVICE_CREDENTIAL = 6;
- /**
- * Notify the receiver to start the fingerprint sensor.
- *
- * This is only applicable to multi-sensor devices that need to delay fingerprint auth
- * (i.e face -> fingerprint).
- */
- int ACTION_START_DELAYED_FINGERPRINT_SENSOR = 7;
/**
* When an action has occurred. The caller will only invoke this when the callback should
@@ -118,66 +114,9 @@ public abstract class AuthBiometricView extends LinearLayout {
void onAction(int action);
}
- @VisibleForTesting
- static class Injector {
- AuthBiometricView mBiometricView;
-
- public Button getNegativeButton() {
- return mBiometricView.findViewById(R.id.button_negative);
- }
-
- public Button getCancelButton() {
- return mBiometricView.findViewById(R.id.button_cancel);
- }
-
- public Button getUseCredentialButton() {
- return mBiometricView.findViewById(R.id.button_use_credential);
- }
-
- public Button getConfirmButton() {
- return mBiometricView.findViewById(R.id.button_confirm);
- }
-
- public Button getTryAgainButton() {
- return mBiometricView.findViewById(R.id.button_try_again);
- }
-
- public TextView getTitleView() {
- return mBiometricView.findViewById(R.id.title);
- }
-
- public TextView getSubtitleView() {
- return mBiometricView.findViewById(R.id.subtitle);
- }
-
- public TextView getDescriptionView() {
- return mBiometricView.findViewById(R.id.description);
- }
-
- public TextView getIndicatorView() {
- return mBiometricView.findViewById(R.id.indicator);
- }
-
- public ImageView getIconView() {
- return mBiometricView.findViewById(R.id.biometric_icon);
- }
-
- public View getIconHolderView() {
- return mBiometricView.findViewById(R.id.biometric_icon_frame);
- }
-
- public int getDelayAfterError() {
- return BiometricPrompt.HIDE_DIALOG_DELAY;
- }
-
- public int getMediumToLargeAnimationDurationMs() {
- return AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS;
- }
- }
-
- private final Injector mInjector;
protected final Handler mHandler;
private final AccessibilityManager mAccessibilityManager;
+ private final LockPatternUtils mLockPatternUtils;
protected final int mTextColorError;
protected final int mTextColorHint;
@@ -195,6 +134,11 @@ public abstract class AuthBiometricView extends LinearLayout {
protected ImageView mIconView;
protected TextView mIndicatorView;
+ @VisibleForTesting @NonNull AuthIconController mIconController;
+ @VisibleForTesting int mAnimationDurationShort = AuthDialog.ANIMATE_SMALL_TO_MEDIUM_DURATION_MS;
+ @VisibleForTesting int mAnimationDurationLong = AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS;
+ @VisibleForTesting int mAnimationDurationHideDialog = BiometricPrompt.HIDE_DIALOG_DELAY;
+
// Negative button position, exclusively for the app-specified behavior
@VisibleForTesting Button mNegativeButton;
// Negative button position, exclusively for cancelling auth after passive auth success
@@ -217,30 +161,7 @@ public abstract class AuthBiometricView extends LinearLayout {
protected boolean mDialogSizeAnimating;
protected Bundle mSavedState;
- /**
- * Delay after authentication is confirmed, before the dialog should be animated away.
- */
- protected abstract int getDelayAfterAuthenticatedDurationMs();
- /**
- * State that the dialog/icon should be in after showing a help message.
- */
- protected abstract int getStateForAfterError();
- /**
- * Invoked when the error message is being cleared.
- */
- protected abstract void handleResetAfterError();
- /**
- * Invoked when the help message is being cleared.
- */
- protected abstract void handleResetAfterHelp();
-
- /**
- * @return true if the dialog supports {@link AuthDialog.DialogSize#SIZE_SMALL}
- */
- protected abstract boolean supportsSmallDialog();
-
private final Runnable mResetErrorRunnable;
-
private final Runnable mResetHelpRunnable;
private final OnClickListener mBackgroundClickListener = (view) -> {
@@ -262,11 +183,6 @@ public abstract class AuthBiometricView extends LinearLayout {
}
public AuthBiometricView(Context context, AttributeSet attrs) {
- this(context, attrs, new Injector());
- }
-
- @VisibleForTesting
- AuthBiometricView(Context context, AttributeSet attrs, Injector injector) {
super(context, attrs);
mHandler = new Handler(Looper.getMainLooper());
mTextColorError = getResources().getColor(
@@ -274,10 +190,8 @@ public abstract class AuthBiometricView extends LinearLayout {
mTextColorHint = getResources().getColor(
R.color.biometric_dialog_gray, context.getTheme());
- mInjector = injector;
- mInjector.mBiometricView = this;
-
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+ mLockPatternUtils = new LockPatternUtils(context);
mResetErrorRunnable = () -> {
updateState(getStateForAfterError());
@@ -292,36 +206,91 @@ public abstract class AuthBiometricView extends LinearLayout {
};
}
- public void setPanelController(AuthPanelController panelController) {
+ /** Delay after authentication is confirmed, before the dialog should be animated away. */
+ protected int getDelayAfterAuthenticatedDurationMs() {
+ return 0;
+ }
+
+ /** State that the dialog/icon should be in after showing a help message. */
+ protected int getStateForAfterError() {
+ return STATE_IDLE;
+ }
+
+ /** Invoked when the error message is being cleared. */
+ protected void handleResetAfterError() {}
+
+ /** Invoked when the help message is being cleared. */
+ protected void handleResetAfterHelp() {}
+
+ /** True if the dialog supports {@link AuthDialog.DialogSize#SIZE_SMALL}. */
+ protected boolean supportsSmallDialog() {
+ return false;
+ }
+
+ /** The string to show when the user must tap to confirm via the button or icon. */
+ @StringRes
+ protected int getConfirmationPrompt() {
+ return R.string.biometric_dialog_tap_confirm;
+ }
+
+ /** True if require confirmation will be honored when set via the API. */
+ protected boolean supportsRequireConfirmation() {
+ return false;
+ }
+
+ /** True if confirmation will be required even if it was not supported/requested. */
+ protected boolean forceRequireConfirmation(@Modality int modality) {
+ return false;
+ }
+
+ /** Ignore all events from this (secondary) modality except successful authentication. */
+ protected boolean ignoreUnsuccessfulEventsFrom(@Modality int modality) {
+ return false;
+ }
+
+ /**
+ * Create the controller for managing the icons transitions during the prompt.
+ *
+ * Subclass should override.
+ */
+ @NonNull
+ protected AuthIconController createIconController() {
+ return new AuthIconController(mContext, mIconView) {
+ @Override
+ public void updateIcon(int lastState, int newState) {}
+ };
+ }
+
+ void setPanelController(AuthPanelController panelController) {
mPanelController = panelController;
}
- public void setPromptInfo(PromptInfo promptInfo) {
+ void setPromptInfo(PromptInfo promptInfo) {
mPromptInfo = promptInfo;
}
- public void setCallback(Callback callback) {
+ void setCallback(Callback callback) {
mCallback = callback;
}
- public void setBackgroundView(View backgroundView) {
+ void setBackgroundView(View backgroundView) {
backgroundView.setOnClickListener(mBackgroundClickListener);
}
- public void setUserId(int userId) {
+ void setUserId(int userId) {
mUserId = userId;
}
- public void setEffectiveUserId(int effectiveUserId) {
+ void setEffectiveUserId(int effectiveUserId) {
mEffectiveUserId = effectiveUserId;
}
- public void setRequireConfirmation(boolean requireConfirmation) {
- mRequireConfirmation = requireConfirmation;
+ void setRequireConfirmation(boolean requireConfirmation) {
+ mRequireConfirmation = requireConfirmation && supportsRequireConfirmation();
}
@VisibleForTesting
- void updateSize(@AuthDialog.DialogSize int newSize) {
+ final void updateSize(@AuthDialog.DialogSize int newSize) {
Log.v(TAG, "Current size: " + mSize + " New size: " + newSize);
if (newSize == AuthDialog.SIZE_SMALL) {
mTitleView.setVisibility(View.GONE);
@@ -376,7 +345,7 @@ public abstract class AuthBiometricView extends LinearLayout {
// Choreograph together
final AnimatorSet as = new AnimatorSet();
- as.setDuration(AuthDialog.ANIMATE_SMALL_TO_MEDIUM_DURATION_MS);
+ as.setDuration(mAnimationDurationShort);
as.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
@@ -429,7 +398,7 @@ public abstract class AuthBiometricView extends LinearLayout {
// Translate at full duration
final ValueAnimator translationAnimator = ValueAnimator.ofFloat(
biometricView.getY(), biometricView.getY() - translationY);
- translationAnimator.setDuration(mInjector.getMediumToLargeAnimationDurationMs());
+ translationAnimator.setDuration(mAnimationDurationLong);
translationAnimator.addUpdateListener((animation) -> {
final float translation = (float) animation.getAnimatedValue();
biometricView.setTranslationY(translation);
@@ -438,7 +407,7 @@ public abstract class AuthBiometricView extends LinearLayout {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
- if (biometricView.getParent() != null) {
+ if (biometricView.getParent() instanceof ViewGroup) {
((ViewGroup) biometricView.getParent()).removeView(biometricView);
}
mSize = newSize;
@@ -447,7 +416,7 @@ public abstract class AuthBiometricView extends LinearLayout {
// Opacity to 0 in half duration
final ValueAnimator opacityAnimator = ValueAnimator.ofFloat(1, 0);
- opacityAnimator.setDuration(mInjector.getMediumToLargeAnimationDurationMs() / 2);
+ opacityAnimator.setDuration(mAnimationDurationLong / 2);
opacityAnimator.addUpdateListener((animation) -> {
final float opacity = (float) animation.getAnimatedValue();
biometricView.setAlpha(opacity);
@@ -457,7 +426,7 @@ public abstract class AuthBiometricView extends LinearLayout {
mPanelController.updateForContentDimensions(
mPanelController.getContainerWidth(),
mPanelController.getContainerHeight(),
- mInjector.getMediumToLargeAnimationDurationMs());
+ mAnimationDurationLong);
// Start the animations together
AnimatorSet as = new AnimatorSet();
@@ -466,7 +435,7 @@ public abstract class AuthBiometricView extends LinearLayout {
animators.add(opacityAnimator);
as.playTogether(animators);
- as.setDuration(mInjector.getMediumToLargeAnimationDurationMs() * 2 / 3);
+ as.setDuration(mAnimationDurationLong * 2 / 3);
as.start();
} else {
Log.e(TAG, "Unknown transition from: " + mSize + " to: " + newSize);
@@ -481,6 +450,8 @@ public abstract class AuthBiometricView extends LinearLayout {
public void updateState(@BiometricState int newState) {
Log.v(TAG, "newState: " + newState);
+ mIconController.updateState(mState, newState);
+
switch (newState) {
case STATE_AUTHENTICATING_ANIMATING_IN:
case STATE_AUTHENTICATING:
@@ -510,10 +481,11 @@ public abstract class AuthBiometricView extends LinearLayout {
mNegativeButton.setVisibility(View.GONE);
mCancelButton.setVisibility(View.VISIBLE);
mUseCredentialButton.setVisibility(View.GONE);
- mConfirmButton.setEnabled(true);
- mConfirmButton.setVisibility(View.VISIBLE);
+ // forced confirmations (multi-sensor) use the icon view as the confirm button
+ mConfirmButton.setEnabled(mRequireConfirmation);
+ mConfirmButton.setVisibility(mRequireConfirmation ? View.VISIBLE : View.GONE);
mIndicatorView.setTextColor(mTextColorHint);
- mIndicatorView.setText(R.string.biometric_dialog_tap_confirm);
+ mIndicatorView.setText(getConfirmationPrompt());
mIndicatorView.setVisibility(View.VISIBLE);
break;
@@ -536,9 +508,9 @@ public abstract class AuthBiometricView extends LinearLayout {
updateState(STATE_AUTHENTICATING);
}
- public void onAuthenticationSucceeded() {
+ public void onAuthenticationSucceeded(@Modality int modality) {
removePendingAnimations();
- if (mRequireConfirmation) {
+ if (mRequireConfirmation || forceRequireConfirmation(modality)) {
updateState(STATE_PENDING_CONFIRMATION);
} else {
updateState(STATE_AUTHENTICATED);
@@ -553,6 +525,10 @@ public abstract class AuthBiometricView extends LinearLayout {
*/
public void onAuthenticationFailed(
@Modality int modality, @Nullable String failureReason) {
+ if (ignoreUnsuccessfulEventsFrom(modality)) {
+ return;
+ }
+
showTemporaryMessage(failureReason, mResetErrorRunnable);
updateState(STATE_ERROR);
}
@@ -564,12 +540,27 @@ public abstract class AuthBiometricView extends LinearLayout {
* @param error message
*/
public void onError(@Modality int modality, String error) {
+ if (ignoreUnsuccessfulEventsFrom(modality)) {
+ return;
+ }
+
showTemporaryMessage(error, mResetErrorRunnable);
updateState(STATE_ERROR);
- mHandler.postDelayed(() -> {
- mCallback.onAction(Callback.ACTION_ERROR);
- }, mInjector.getDelayAfterError());
+ mHandler.postDelayed(() -> mCallback.onAction(Callback.ACTION_ERROR),
+ mAnimationDurationHideDialog);
+ }
+
+ /**
+ * Fingerprint pointer down event. This does nothing by default and will not be called if the
+ * device does not have an appropriate sensor (UDFPS), but it may be used as an alternative
+ * to the "retry" button when fingerprint is used with other modalities.
+ *
+ * @param failedModalities the set of modalities that have failed
+ * @return true if a retry was initiated as a result of this event
+ */
+ public boolean onPointerDown(Set<Integer> failedModalities) {
+ return false;
}
/**
@@ -579,6 +570,9 @@ public abstract class AuthBiometricView extends LinearLayout {
* @param help message
*/
public void onHelp(@Modality int modality, String help) {
+ if (ignoreUnsuccessfulEventsFrom(modality)) {
+ return;
+ }
if (mSize != AuthDialog.SIZE_MEDIUM) {
Log.w(TAG, "Help received in size: " + mSize);
return;
@@ -639,7 +633,7 @@ public abstract class AuthBiometricView extends LinearLayout {
// select to enable marquee unless a screen reader is enabled
mIndicatorView.setSelected(!mAccessibilityManager.isEnabled()
|| !mAccessibilityManager.isTouchExplorationEnabled());
- mHandler.postDelayed(resetMessageRunnable, mInjector.getDelayAfterError());
+ mHandler.postDelayed(resetMessageRunnable, mAnimationDurationHideDialog);
Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
}
@@ -647,29 +641,22 @@ public abstract class AuthBiometricView extends LinearLayout {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- onFinishInflateInternal();
- }
- /**
- * After inflation, but before things like restoreState, onAttachedToWindow, etc.
- */
- @VisibleForTesting
- void onFinishInflateInternal() {
- mTitleView = mInjector.getTitleView();
- mSubtitleView = mInjector.getSubtitleView();
- mDescriptionView = mInjector.getDescriptionView();
- mIconView = mInjector.getIconView();
- mIconHolderView = mInjector.getIconHolderView();
- mIndicatorView = mInjector.getIndicatorView();
+ mTitleView = findViewById(R.id.title);
+ mSubtitleView = findViewById(R.id.subtitle);
+ mDescriptionView = findViewById(R.id.description);
+ mIconView = findViewById(R.id.biometric_icon);
+ mIconHolderView = findViewById(R.id.biometric_icon_frame);
+ mIndicatorView = findViewById(R.id.indicator);
// Negative-side (left) buttons
- mNegativeButton = mInjector.getNegativeButton();
- mCancelButton = mInjector.getCancelButton();
- mUseCredentialButton = mInjector.getUseCredentialButton();
+ mNegativeButton = findViewById(R.id.button_negative);
+ mCancelButton = findViewById(R.id.button_cancel);
+ mUseCredentialButton = findViewById(R.id.button_use_credential);
// Positive-side (right) buttons
- mConfirmButton = mInjector.getConfirmButton();
- mTryAgainButton = mInjector.getTryAgainButton();
+ mConfirmButton = findViewById(R.id.button_confirm);
+ mTryAgainButton = findViewById(R.id.button_try_again);
mNegativeButton.setOnClickListener((view) -> {
mCallback.onAction(Callback.ACTION_BUTTON_NEGATIVE);
@@ -693,6 +680,15 @@ public abstract class AuthBiometricView extends LinearLayout {
mTryAgainButton.setVisibility(View.GONE);
Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
});
+
+ mIconController = createIconController();
+ if (mIconController.getActsAsConfirmButton()) {
+ mIconView.setOnClickListener((view) -> {
+ if (mState == STATE_PENDING_CONFIRMATION) {
+ updateState(STATE_AUTHENTICATED);
+ }
+ });
+ }
}
/**
@@ -706,21 +702,13 @@ public abstract class AuthBiometricView extends LinearLayout {
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- onAttachedToWindowInternal();
- }
- /**
- * Contains all the testable logic that should be invoked when {@link #onAttachedToWindow()} is
- * invoked.
- */
- @VisibleForTesting
- void onAttachedToWindowInternal() {
mTitleView.setText(mPromptInfo.getTitle());
if (isDeviceCredentialAllowed()) {
final CharSequence credentialButtonText;
- final @Utils.CredentialType int credentialType =
- Utils.getCredentialType(mContext, mEffectiveUserId);
+ @Utils.CredentialType final int credentialType =
+ Utils.getCredentialType(mLockPatternUtils, mEffectiveUserId);
switch (credentialType) {
case Utils.CREDENTIAL_PIN:
credentialButtonText =
@@ -731,9 +719,6 @@ public abstract class AuthBiometricView extends LinearLayout {
getResources().getString(R.string.biometric_dialog_use_pattern);
break;
case Utils.CREDENTIAL_PASSWORD:
- credentialButtonText =
- getResources().getString(R.string.biometric_dialog_use_password);
- break;
default:
credentialButtonText =
getResources().getString(R.string.biometric_dialog_use_password);
@@ -749,7 +734,6 @@ public abstract class AuthBiometricView extends LinearLayout {
}
setTextOrHide(mSubtitleView, mPromptInfo.getSubtitle());
-
setTextOrHide(mDescriptionView, mPromptInfo.getDescription());
if (mSavedState == null) {
@@ -774,6 +758,8 @@ public abstract class AuthBiometricView extends LinearLayout {
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
+ mIconController.setDeactivated(true);
+
// Empty the handler, otherwise things like ACTION_AUTHENTICATED may be duplicated once
// the new dialog is restored.
mHandler.removeCallbacksAndMessages(null /* all */);
@@ -856,15 +842,7 @@ public abstract class AuthBiometricView extends LinearLayout {
@Override
public void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- onLayoutInternal();
- }
- /**
- * Contains all the testable logic that should be invoked when
- * {@link #onLayout(boolean, int, int, int, int)}, is invoked.
- */
- @VisibleForTesting
- void onLayoutInternal() {
// Start with initial size only once. Subsequent layout changes don't matter since we
// only care about the initial icon position.
if (mIconOriginalY == 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 21edb2478c79..6b6af4c7b52f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -16,9 +16,10 @@
package com.android.systemui.biometrics;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
import static android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
+import android.annotation.DurationMillisLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -52,13 +53,16 @@ import android.widget.LinearLayout;
import android.widget.ScrollView;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Top level container/controller for the BiometricPrompt UI.
@@ -66,54 +70,52 @@ import java.util.List;
public class AuthContainerView extends LinearLayout
implements AuthDialog, WakefulnessLifecycle.Observer {
- private static final String TAG = "BiometricPrompt/AuthContainerView";
+ private static final String TAG = "AuthContainerView";
+
private static final int ANIMATION_DURATION_SHOW_MS = 250;
- private static final int ANIMATION_DURATION_AWAY_MS = 350; // ms
+ private static final int ANIMATION_DURATION_AWAY_MS = 350;
- static final int STATE_UNKNOWN = 0;
- static final int STATE_ANIMATING_IN = 1;
- static final int STATE_PENDING_DISMISS = 2;
- static final int STATE_SHOWING = 3;
- static final int STATE_ANIMATING_OUT = 4;
- static final int STATE_GONE = 5;
+ private static final int STATE_UNKNOWN = 0;
+ private static final int STATE_ANIMATING_IN = 1;
+ private static final int STATE_PENDING_DISMISS = 2;
+ private static final int STATE_SHOWING = 3;
+ private static final int STATE_ANIMATING_OUT = 4;
+ private static final int STATE_GONE = 5;
@Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_UNKNOWN, STATE_ANIMATING_IN, STATE_PENDING_DISMISS, STATE_SHOWING,
STATE_ANIMATING_OUT, STATE_GONE})
- @interface ContainerState {}
+ private @interface ContainerState {}
- final Config mConfig;
- final int mEffectiveUserId;
- @Nullable private final List<FingerprintSensorPropertiesInternal> mFpProps;
- @Nullable private final List<FaceSensorPropertiesInternal> mFaceProps;
+ private final Config mConfig;
+ private final int mEffectiveUserId;
private final Handler mHandler;
- private final Injector mInjector;
private final IBinder mWindowToken = new Binder();
private final WindowManager mWindowManager;
- private final AuthPanelController mPanelController;
private final Interpolator mLinearOutSlowIn;
- @VisibleForTesting final BiometricCallback mBiometricCallback;
private final CredentialCallback mCredentialCallback;
+ private final LockPatternUtils mLockPatternUtils;
+ private final WakefulnessLifecycle mWakefulnessLifecycle;
- @VisibleForTesting final FrameLayout mFrameLayout;
- @VisibleForTesting @Nullable AuthBiometricView mBiometricView;
- @VisibleForTesting @Nullable AuthCredentialView mCredentialView;
+ @VisibleForTesting final BiometricCallback mBiometricCallback;
- @VisibleForTesting final ImageView mBackgroundView;
- @VisibleForTesting final ScrollView mBiometricScrollView;
+ @Nullable private AuthBiometricView mBiometricView;
+ @Nullable private AuthCredentialView mCredentialView;
+ private final AuthPanelController mPanelController;
+ private final FrameLayout mFrameLayout;
+ private final ImageView mBackgroundView;
+ private final ScrollView mBiometricScrollView;
private final View mPanelView;
-
private final float mTranslationY;
-
- private final WakefulnessLifecycle mWakefulnessLifecycle;
-
- @VisibleForTesting @ContainerState int mContainerState = STATE_UNKNOWN;
+ @ContainerState private int mContainerState = STATE_UNKNOWN;
+ private final Set<Integer> mFailedModalities = new HashSet<Integer>();
// Non-null only if the dialog is in the act of dismissing and has not sent the reason yet.
- @Nullable @AuthDialogCallback.DismissedReason Integer mPendingCallbackReason;
+ @Nullable @AuthDialogCallback.DismissedReason private Integer mPendingCallbackReason;
// HAT received from LockSettingsService when credential is verified.
- @Nullable byte[] mCredentialAttestation;
+ @Nullable private byte[] mCredentialAttestation;
+ @VisibleForTesting
static class Config {
Context mContext;
AuthDialogCallback mCallback;
@@ -122,11 +124,11 @@ public class AuthContainerView extends LinearLayout
int mUserId;
String mOpPackageName;
int[] mSensorIds;
- boolean mCredentialAllowed;
boolean mSkipIntro;
long mOperationId;
long mRequestId;
- @BiometricMultiSensorMode int mMultiSensorConfig;
+ boolean mSkipAnimation = false;
+ @BiometricMultiSensorMode int mMultiSensorConfig = BIOMETRIC_MULTI_SENSOR_DEFAULT;
}
public static class Builder {
@@ -167,7 +169,7 @@ public class AuthContainerView extends LinearLayout
return this;
}
- public Builder setOperationId(long operationId) {
+ public Builder setOperationId(@DurationMillisLong long operationId) {
mConfig.mOperationId = operationId;
return this;
}
@@ -178,55 +180,27 @@ public class AuthContainerView extends LinearLayout
return this;
}
+ @VisibleForTesting
+ public Builder setSkipAnimationDuration(boolean skip) {
+ mConfig.mSkipAnimation = skip;
+ return this;
+ }
+
/** The multi-sensor mode. */
public Builder setMultiSensorConfig(@BiometricMultiSensorMode int multiSensorConfig) {
mConfig.mMultiSensorConfig = multiSensorConfig;
return this;
}
- public AuthContainerView build(int[] sensorIds, boolean credentialAllowed,
+ public AuthContainerView build(int[] sensorIds,
@Nullable List<FingerprintSensorPropertiesInternal> fpProps,
@Nullable List<FaceSensorPropertiesInternal> faceProps,
- WakefulnessLifecycle wakefulnessLifecycle) {
+ @NonNull WakefulnessLifecycle wakefulnessLifecycle,
+ @NonNull UserManager userManager,
+ @NonNull LockPatternUtils lockPatternUtils) {
mConfig.mSensorIds = sensorIds;
- mConfig.mCredentialAllowed = credentialAllowed;
- return new AuthContainerView(
- mConfig, new Injector(), fpProps, faceProps, wakefulnessLifecycle);
- }
- }
-
- public static class Injector {
- ScrollView getBiometricScrollView(FrameLayout parent) {
- return parent.findViewById(R.id.biometric_scrollview);
- }
-
- FrameLayout inflateContainerView(LayoutInflater factory, ViewGroup root) {
- return (FrameLayout) factory.inflate(
- R.layout.auth_container_view, root, false /* attachToRoot */);
- }
-
- AuthPanelController getPanelController(Context context, View panelView) {
- return new AuthPanelController(context, panelView);
- }
-
- ImageView getBackgroundView(FrameLayout parent) {
- return parent.findViewById(R.id.background);
- }
-
- View getPanelView(FrameLayout parent) {
- return parent.findViewById(R.id.panel);
- }
-
- int getAnimateCredentialStartDelayMs() {
- return AuthDialog.ANIMATE_CREDENTIAL_START_DELAY_MS;
- }
-
- UserManager getUserManager(Context context) {
- return UserManager.get(context);
- }
-
- int getCredentialType(Context context, int effectiveUserId) {
- return Utils.getCredentialType(context, effectiveUserId);
+ return new AuthContainerView(mConfig, fpProps, faceProps, wakefulnessLifecycle,
+ userManager, lockPatternUtils, new Handler(Looper.getMainLooper()));
}
}
@@ -246,6 +220,7 @@ public class AuthContainerView extends LinearLayout
animateAway(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
break;
case AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN:
+ mFailedModalities.clear();
mConfig.mCallback.onTryAgainPressed();
break;
case AuthBiometricView.Callback.ACTION_ERROR:
@@ -255,10 +230,7 @@ public class AuthContainerView extends LinearLayout
mConfig.mCallback.onDeviceCredentialPressed();
mHandler.postDelayed(() -> {
addCredentialView(false /* animatePanel */, true /* animateContents */);
- }, mInjector.getAnimateCredentialStartDelayMs());
- break;
- case AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR:
- mConfig.mCallback.onStartFingerprintNow();
+ }, mConfig.mSkipAnimation ? 0 : AuthDialog.ANIMATE_CREDENTIAL_START_DELAY_MS);
break;
default:
Log.e(TAG, "Unhandled action: " + action);
@@ -275,21 +247,19 @@ public class AuthContainerView extends LinearLayout
}
@VisibleForTesting
- AuthContainerView(Config config, Injector injector,
+ AuthContainerView(Config config,
@Nullable List<FingerprintSensorPropertiesInternal> fpProps,
@Nullable List<FaceSensorPropertiesInternal> faceProps,
- WakefulnessLifecycle wakefulnessLifecycle) {
+ @NonNull WakefulnessLifecycle wakefulnessLifecycle,
+ @NonNull UserManager userManager,
+ @NonNull LockPatternUtils lockPatternUtils,
+ @NonNull Handler mainHandler) {
super(config.mContext);
mConfig = config;
- mInjector = injector;
- mFpProps = fpProps;
- mFaceProps = faceProps;
-
- mEffectiveUserId = mInjector.getUserManager(mContext)
- .getCredentialOwnerProfile(mConfig.mUserId);
-
- mHandler = new Handler(Looper.getMainLooper());
+ mLockPatternUtils = lockPatternUtils;
+ mEffectiveUserId = userManager.getCredentialOwnerProfile(mConfig.mUserId);
+ mHandler = mainHandler;
mWindowManager = mContext.getSystemService(WindowManager.class);
mWakefulnessLifecycle = wakefulnessLifecycle;
@@ -299,100 +269,42 @@ public class AuthContainerView extends LinearLayout
mBiometricCallback = new BiometricCallback();
mCredentialCallback = new CredentialCallback();
- final LayoutInflater factory = LayoutInflater.from(mContext);
- mFrameLayout = mInjector.inflateContainerView(factory, this);
-
- mPanelView = mInjector.getPanelView(mFrameLayout);
- mPanelController = mInjector.getPanelController(mContext, mPanelView);
+ final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+ mFrameLayout = (FrameLayout) layoutInflater.inflate(
+ R.layout.auth_container_view, this, false /* attachToRoot */);
+ addView(mFrameLayout);
+ mBiometricScrollView = mFrameLayout.findViewById(R.id.biometric_scrollview);
+ mBackgroundView = mFrameLayout.findViewById(R.id.background);
+ mPanelView = mFrameLayout.findViewById(R.id.panel);
+ mPanelController = new AuthPanelController(mContext, mPanelView);
// Inflate biometric view only if necessary.
- final int sensorCount = config.mSensorIds.length;
if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
- if (sensorCount == 1) {
- final int singleSensorAuthId = config.mSensorIds[0];
- if (Utils.containsSensorId(mFpProps, singleSensorAuthId)) {
- FingerprintSensorPropertiesInternal sensorProps = null;
- for (FingerprintSensorPropertiesInternal prop : mFpProps) {
- if (prop.sensorId == singleSensorAuthId) {
- sensorProps = prop;
- break;
- }
- }
-
- if (sensorProps.isAnyUdfpsType()) {
- AuthBiometricUdfpsView udfpsView = (AuthBiometricUdfpsView) factory
- .inflate(R.layout.auth_biometric_udfps_view, null, false);
- udfpsView.setSensorProps(sensorProps);
- mBiometricView = udfpsView;
- } else {
- mBiometricView = (AuthBiometricFingerprintView) factory
- .inflate(R.layout.auth_biometric_fingerprint_view, null, false);
- }
- } else if (Utils.containsSensorId(mFaceProps, singleSensorAuthId)) {
- mBiometricView = (AuthBiometricFaceView)
- factory.inflate(R.layout.auth_biometric_face_view, null, false);
- } else {
- // Unknown sensorId
- Log.e(TAG, "Unknown sensorId: " + singleSensorAuthId);
- mBiometricView = null;
- mBackgroundView = null;
- mBiometricScrollView = null;
- return;
- }
- } else if (sensorCount == 2) {
- final int[] allSensors = findFaceAndFingerprintSensors();
- final int faceSensorId = allSensors[0];
- final int fingerprintSensorId = allSensors[1];
-
- if (fingerprintSensorId == -1 || faceSensorId == -1) {
- Log.e(TAG, "Missing fingerprint or face for dual-sensor config");
- mBiometricView = null;
- mBackgroundView = null;
- mBiometricScrollView = null;
- return;
- }
-
- FingerprintSensorPropertiesInternal fingerprintSensorProps = null;
- for (FingerprintSensorPropertiesInternal prop : mFpProps) {
- if (prop.sensorId == fingerprintSensorId) {
- fingerprintSensorProps = prop;
- break;
- }
- }
-
- if (fingerprintSensorProps != null) {
- final AuthBiometricFaceToFingerprintView faceToFingerprintView =
- (AuthBiometricFaceToFingerprintView) factory.inflate(
- R.layout.auth_biometric_face_to_fingerprint_view, null, false);
- faceToFingerprintView.setFingerprintSensorProps(fingerprintSensorProps);
- faceToFingerprintView.setModalityListener(new ModalityListener() {
- @Override
- public void onModalitySwitched(int oldModality, int newModality) {
- maybeUpdatePositionForUdfps(true /* invalidate */);
- }
- });
- mBiometricView = faceToFingerprintView;
- } else {
- Log.e(TAG, "Fingerprint props not found for sensor ID: " + fingerprintSensorId);
- mBiometricView = null;
- mBackgroundView = null;
- mBiometricScrollView = null;
- return;
- }
+ final FingerprintSensorPropertiesInternal fpProperties =
+ Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds);
+ final FaceSensorPropertiesInternal faceProperties =
+ Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds);
+
+ if (fpProperties != null && faceProperties != null) {
+ final AuthBiometricFingerprintAndFaceView fingerprintAndFaceView =
+ (AuthBiometricFingerprintAndFaceView) layoutInflater.inflate(
+ R.layout.auth_biometric_fingerprint_and_face_view, null, false);
+ fingerprintAndFaceView.setSensorProperties(fpProperties);
+ mBiometricView = fingerprintAndFaceView;
+ } else if (fpProperties != null) {
+ final AuthBiometricFingerprintView fpView =
+ (AuthBiometricFingerprintView) layoutInflater.inflate(
+ R.layout.auth_biometric_fingerprint_view, null, false);
+ fpView.setSensorProperties(fpProperties);
+ mBiometricView = fpView;
+ } else if (faceProperties != null) {
+ mBiometricView = (AuthBiometricFaceView) layoutInflater.inflate(
+ R.layout.auth_biometric_face_view, null, false);
} else {
- Log.e(TAG, "Unsupported sensor array, length: " + sensorCount);
- mBiometricView = null;
- mBackgroundView = null;
- mBiometricScrollView = null;
- return;
+ Log.e(TAG, "No sensors found!");
}
}
- mBiometricScrollView = mInjector.getBiometricScrollView(mFrameLayout);
- mBackgroundView = mInjector.getBackgroundView(mFrameLayout);
-
- addView(mFrameLayout);
-
// init view before showing
if (mBiometricView != null) {
mBiometricView.setRequireConfirmation(mConfig.mRequireConfirmation);
@@ -431,10 +343,6 @@ public class AuthContainerView extends LinearLayout
return Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo);
}
- private void addBiometricView() {
- mBiometricScrollView.addView(mBiometricView);
- }
-
/**
* Adds the credential view. When going from biometric to credential view, the biometric
* view starts the panel expansion animation. If the credential view is being shown first,
@@ -444,8 +352,8 @@ public class AuthContainerView extends LinearLayout
private void addCredentialView(boolean animatePanel, boolean animateContents) {
final LayoutInflater factory = LayoutInflater.from(mContext);
- final @Utils.CredentialType int credentialType = mInjector.getCredentialType(
- mContext, mEffectiveUserId);
+ @Utils.CredentialType final int credentialType = Utils.getCredentialType(
+ mLockPatternUtils, mEffectiveUserId);
switch (credentialType) {
case Utils.CREDENTIAL_PATTERN:
@@ -493,15 +401,11 @@ public class AuthContainerView extends LinearLayout
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
- onAttachedToWindowInternal();
- }
- @VisibleForTesting
- void onAttachedToWindowInternal() {
mWakefulnessLifecycle.addObserver(this);
if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
- addBiometricView();
+ mBiometricScrollView.addView(mBiometricView);
} else if (Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) {
addCredentialView(true /* animatePanel */, false /* animateContents */);
} else {
@@ -521,17 +425,18 @@ public class AuthContainerView extends LinearLayout
mBiometricScrollView.setY(mTranslationY);
setAlpha(0f);
+ final long animateDuration = mConfig.mSkipAnimation ? 0 : ANIMATION_DURATION_SHOW_MS;
postOnAnimation(() -> {
mPanelView.animate()
.translationY(0)
- .setDuration(ANIMATION_DURATION_SHOW_MS)
+ .setDuration(animateDuration)
.setInterpolator(mLinearOutSlowIn)
.withLayer()
.withEndAction(this::onDialogAnimatedIn)
.start();
mBiometricScrollView.animate()
.translationY(0)
- .setDuration(ANIMATION_DURATION_SHOW_MS)
+ .setDuration(animateDuration)
.setInterpolator(mLinearOutSlowIn)
.withLayer()
.start();
@@ -539,14 +444,14 @@ public class AuthContainerView extends LinearLayout
mCredentialView.setY(mTranslationY);
mCredentialView.animate()
.translationY(0)
- .setDuration(ANIMATION_DURATION_SHOW_MS)
+ .setDuration(animateDuration)
.setInterpolator(mLinearOutSlowIn)
.withLayer()
.start();
}
animate()
.alpha(1f)
- .setDuration(ANIMATION_DURATION_SHOW_MS)
+ .setDuration(animateDuration)
.setInterpolator(mLinearOutSlowIn)
.withLayer()
.start();
@@ -555,15 +460,8 @@ public class AuthContainerView extends LinearLayout
}
private static boolean shouldUpdatePositionForUdfps(@NonNull View view) {
- if (view instanceof AuthBiometricUdfpsView) {
- return true;
- }
-
- if (view instanceof AuthBiometricFaceToFingerprintView) {
- AuthBiometricFaceToFingerprintView faceToFingerprintView =
- (AuthBiometricFaceToFingerprintView) view;
- return faceToFingerprintView.getActiveSensorType() == TYPE_FINGERPRINT
- && faceToFingerprintView.isFingerprintUdfps();
+ if (view instanceof AuthBiometricFingerprintView) {
+ return ((AuthBiometricFingerprintView) view).isUdfps();
}
return false;
@@ -652,12 +550,13 @@ public class AuthContainerView extends LinearLayout
}
@Override
- public void onAuthenticationSucceeded() {
- mBiometricView.onAuthenticationSucceeded();
+ public void onAuthenticationSucceeded(@Modality int modality) {
+ mBiometricView.onAuthenticationSucceeded(modality);
}
@Override
public void onAuthenticationFailed(@Modality int modality, String failureReason) {
+ mFailedModalities.add(modality);
mBiometricView.onAuthenticationFailed(modality, failureReason);
}
@@ -672,8 +571,17 @@ public class AuthContainerView extends LinearLayout
}
@Override
+ public void onPointerDown() {
+ if (mBiometricView.onPointerDown(mFailedModalities)) {
+ Log.d(TAG, "retrying failed modalities (pointer down)");
+ mBiometricCallback.onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
+ }
+ }
+
+ @Override
public void onSaveState(@NonNull Bundle outState) {
- outState.putInt(AuthDialog.KEY_CONTAINER_STATE, mContainerState);
+ outState.putBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY,
+ mContainerState == STATE_ANIMATING_OUT);
// In the case where biometric and credential are both allowed, we can assume that
// biometric isn't showing if credential is showing since biometric is shown first.
outState.putBoolean(AuthDialog.KEY_BIOMETRIC_SHOWING,
@@ -695,8 +603,7 @@ public class AuthContainerView extends LinearLayout
mBiometricView.startTransitionToCredentialUI();
}
- @VisibleForTesting
- void animateAway(int reason) {
+ void animateAway(@AuthDialogCallback.DismissedReason int reason) {
animateAway(true /* sendReason */, reason);
}
@@ -724,31 +631,32 @@ public class AuthContainerView extends LinearLayout
removeWindowIfAttached();
};
+ final long animateDuration = mConfig.mSkipAnimation ? 0 : ANIMATION_DURATION_AWAY_MS;
postOnAnimation(() -> {
mPanelView.animate()
.translationY(mTranslationY)
- .setDuration(ANIMATION_DURATION_AWAY_MS)
+ .setDuration(animateDuration)
.setInterpolator(mLinearOutSlowIn)
.withLayer()
.withEndAction(endActionRunnable)
.start();
mBiometricScrollView.animate()
.translationY(mTranslationY)
- .setDuration(ANIMATION_DURATION_AWAY_MS)
+ .setDuration(animateDuration)
.setInterpolator(mLinearOutSlowIn)
.withLayer()
.start();
if (mCredentialView != null && mCredentialView.isAttachedToWindow()) {
mCredentialView.animate()
.translationY(mTranslationY)
- .setDuration(ANIMATION_DURATION_AWAY_MS)
+ .setDuration(animateDuration)
.setInterpolator(mLinearOutSlowIn)
.withLayer()
.start();
}
animate()
.alpha(0f)
- .setDuration(ANIMATION_DURATION_AWAY_MS)
+ .setDuration(animateDuration)
.setInterpolator(mLinearOutSlowIn)
.withLayer()
.start();
@@ -773,8 +681,7 @@ public class AuthContainerView extends LinearLayout
mWindowManager.removeView(this);
}
- @VisibleForTesting
- void onDialogAnimatedIn() {
+ private void onDialogAnimatedIn() {
if (mContainerState == STATE_PENDING_DISMISS) {
Log.d(TAG, "onDialogAnimatedIn(): mPendingDismissDialog=true, dismissing now");
animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
@@ -788,8 +695,7 @@ public class AuthContainerView extends LinearLayout
}
@VisibleForTesting
- static WindowManager.LayoutParams getLayoutParams(IBinder windowToken,
- CharSequence title) {
+ static WindowManager.LayoutParams getLayoutParams(IBinder windowToken, CharSequence title) {
final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
| WindowManager.LayoutParams.FLAG_SECURE;
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
@@ -805,24 +711,4 @@ public class AuthContainerView extends LinearLayout
lp.token = windowToken;
return lp;
}
-
- // returns [face, fingerprint] sensor ids (id is -1 if not present)
- private int[] findFaceAndFingerprintSensors() {
- int faceSensorId = -1;
- int fingerprintSensorId = -1;
-
- for (final int sensorId : mConfig.mSensorIds) {
- if (Utils.containsSensorId(mFpProps, sensorId)) {
- fingerprintSensorId = sensorId;
- } else if (Utils.containsSensorId(mFaceProps, sensorId)) {
- faceSensorId = sensorId;
- }
-
- if (fingerprintSensorId != -1 && faceSensorId != -1) {
- break;
- }
- }
-
- return new int[] {faceSensorId, fingerprintSensorId};
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index dfb8c18b4ece..64c2d2e3858e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -51,6 +51,7 @@ import android.hardware.fingerprint.IUdfpsHbmListener;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
+import android.os.UserManager;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.MotionEvent;
@@ -59,6 +60,7 @@ import android.view.WindowManager;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
+import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.CoreStartable;
import com.android.systemui.assist.ui.DisplayUtils;
import com.android.systemui.dagger.SysUISingleton;
@@ -123,8 +125,6 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
@Nullable private SidefpsController mSidefpsController;
@Nullable private IBiometricContextListener mBiometricContextListener;
@VisibleForTesting
- TaskStackListener mTaskStackListener;
- @VisibleForTesting
IBiometricSysuiReceiver mReceiver;
@VisibleForTesting
@NonNull final BiometricDisplayListener mOrientationListener;
@@ -137,13 +137,16 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
@NonNull private final SensorPrivacyManager mSensorPrivacyManager;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private boolean mAllAuthenticatorsRegistered;
+ @NonNull private final UserManager mUserManager;
+ @NonNull private final LockPatternUtils mLockPatternUtils;
- private class BiometricTaskStackListener extends TaskStackListener {
+ @VisibleForTesting
+ final TaskStackListener mTaskStackListener = new TaskStackListener() {
@Override
public void onTaskStackChanged() {
mHandler.post(AuthController.this::handleTaskStackChanged);
}
- }
+ };
private final IFingerprintAuthenticatorsRegisteredCallback
mFingerprintAuthenticatorsRegisteredCallback =
@@ -256,6 +259,17 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
mUdfpsProps = !udfpsProps.isEmpty() ? udfpsProps : null;
if (mUdfpsProps != null) {
mUdfpsController = mUdfpsControllerFactory.get();
+ mUdfpsController.addCallback(new UdfpsController.Callback() {
+ @Override
+ public void onFingerUp() {}
+
+ @Override
+ public void onFingerDown() {
+ if (mCurrentDialog != null) {
+ mCurrentDialog.onPointerDown();
+ }
+ }
+ });
}
mSidefpsProps = !sidefpsProps.isEmpty() ? sidefpsProps : null;
if (mSidefpsProps != null) {
@@ -360,20 +374,6 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
}
@Override
- public void onStartFingerprintNow() {
- if (mReceiver == null) {
- Log.e(TAG, "onStartUdfpsNow: Receiver is null");
- return;
- }
-
- try {
- mReceiver.onStartFingerprintNow();
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException when sending onDialogAnimatedIn", e);
- }
- }
-
- @Override
public void onDismissed(@DismissedReason int reason, @Nullable byte[] credentialAttestation) {
switch (reason) {
case AuthDialogCallback.DISMISSED_USER_CANCELED:
@@ -503,12 +503,16 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
Provider<UdfpsController> udfpsControllerFactory,
Provider<SidefpsController> sidefpsControllerFactory,
@NonNull DisplayManager displayManager,
- WakefulnessLifecycle wakefulnessLifecycle,
+ @NonNull WakefulnessLifecycle wakefulnessLifecycle,
+ @NonNull UserManager userManager,
+ @NonNull LockPatternUtils lockPatternUtils,
@NonNull StatusBarStateController statusBarStateController,
@Main Handler handler) {
super(context);
mExecution = execution;
mWakefulnessLifecycle = wakefulnessLifecycle;
+ mUserManager = userManager;
+ mLockPatternUtils = lockPatternUtils;
mHandler = handler;
mCommandQueue = commandQueue;
mActivityTaskManager = activityTaskManager;
@@ -583,7 +587,6 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
mFingerprintAuthenticatorsRegisteredCallback);
}
- mTaskStackListener = new BiometricTaskStackListener();
mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
}
@@ -668,11 +671,11 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
* example, KeyguardUpdateMonitor has its own {@link FingerprintManager.AuthenticationCallback}.
*/
@Override
- public void onBiometricAuthenticated() {
+ public void onBiometricAuthenticated(@Modality int modality) {
if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: ");
if (mCurrentDialog != null) {
- mCurrentDialog.onAuthenticationSucceeded();
+ mCurrentDialog.onAuthenticationSucceeded(modality);
} else {
Log.w(TAG, "onBiometricAuthenticated callback but dialog gone");
}
@@ -827,7 +830,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
final String opPackageName = (String) args.arg6;
final long operationId = args.argl1;
final long requestId = args.argl2;
- final @BiometricMultiSensorMode int multiSensorConfig = args.argi2;
+ @BiometricMultiSensorMode final int multiSensorConfig = args.argi2;
// Create a new dialog but do not replace the current one yet.
final AuthDialog newDialog = buildDialog(
@@ -835,13 +838,14 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
requireConfirmation,
userId,
sensorIds,
- credentialAllowed,
opPackageName,
skipAnimation,
operationId,
requestId,
multiSensorConfig,
- mWakefulnessLifecycle);
+ mWakefulnessLifecycle,
+ mUserManager,
+ mLockPatternUtils);
if (newDialog == null) {
Log.e(TAG, "Unsupported type configuration");
@@ -902,8 +906,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
// Only show the dialog if necessary. If it was animating out, the dialog is supposed
// to send its pending callback immediately.
- if (savedState.getInt(AuthDialog.KEY_CONTAINER_STATE)
- != AuthContainerView.STATE_ANIMATING_OUT) {
+ if (!savedState.getBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY, false)) {
final boolean credentialShowing =
savedState.getBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING);
if (credentialShowing) {
@@ -927,10 +930,12 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
}
protected AuthDialog buildDialog(PromptInfo promptInfo, boolean requireConfirmation,
- int userId, int[] sensorIds, boolean credentialAllowed, String opPackageName,
+ int userId, int[] sensorIds, String opPackageName,
boolean skipIntro, long operationId, long requestId,
@BiometricMultiSensorMode int multiSensorConfig,
- WakefulnessLifecycle wakefulnessLifecycle) {
+ @NonNull WakefulnessLifecycle wakefulnessLifecycle,
+ @NonNull UserManager userManager,
+ @NonNull LockPatternUtils lockPatternUtils) {
return new AuthContainerView.Builder(mContext)
.setCallback(this)
.setPromptInfo(promptInfo)
@@ -941,7 +946,8 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
.setOperationId(operationId)
.setRequestId(requestId)
.setMultiSensorConfig(multiSensorConfig)
- .build(sensorIds, credentialAllowed, mFpProps, mFaceProps, wakefulnessLifecycle);
+ .build(sensorIds, mFpProps, mFaceProps, wakefulnessLifecycle, userManager,
+ lockPatternUtils);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
index fa5213e94081..59ed156bce33 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
@@ -31,7 +31,7 @@ import java.lang.annotation.RetentionPolicy;
*/
public interface AuthDialog {
- String KEY_CONTAINER_STATE = "container_state";
+ String KEY_CONTAINER_GOING_AWAY = "container_going_away";
String KEY_BIOMETRIC_SHOWING = "biometric_showing";
String KEY_CREDENTIAL_SHOWING = "credential_showing";
@@ -64,7 +64,7 @@ public interface AuthDialog {
@interface DialogSize {}
/**
- * Parameters used when laying out {@link AuthBiometricView}, its sublclasses, and
+ * Parameters used when laying out {@link AuthBiometricView}, its subclasses, and
* {@link AuthPanelController}.
*/
class LayoutParams {
@@ -113,7 +113,7 @@ public interface AuthDialog {
/**
* Biometric authenticated. May be pending user confirmation, or completed.
*/
- void onAuthenticationSucceeded();
+ void onAuthenticationSucceeded(@Modality int modality);
/**
* Authentication failed (reject, timeout). Dialog stays showing.
@@ -136,6 +136,9 @@ public interface AuthDialog {
*/
void onError(@Modality int modality, String error);
+ /** UDFPS pointer down event. */
+ void onPointerDown();
+
/**
* Save the current state.
* @param outState
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
index 9f40ca7b0346..a7d2901b21c3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
@@ -70,9 +70,4 @@ public interface AuthDialogCallback {
* Notifies when the dialog has finished animating.
*/
void onDialogAnimatedIn();
-
- /**
- * Notifies that the fingerprint sensor should be started now.
- */
- void onStartFingerprintNow();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt
index 6607915fac9d..242601d46fa4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt
@@ -23,7 +23,7 @@ import android.util.AttributeSet
*
* Currently doesn't draw anything.
*
- * Note that [AuthBiometricUdfpsView] also shows UDFPS animations. At some point we should
+ * Note that [AuthBiometricFingerprintViewController] also shows UDFPS animations. At some point we should
* de-dupe this if necessary.
*/
class UdfpsBpView(context: Context, attrs: AttributeSet?) : UdfpsAnimationView(context, attrs) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 590963b2ff48..086894d2e670 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -300,7 +300,10 @@ class UdfpsControllerOverlay(
when (context.display!!.rotation) {
Surface.ROTATION_90 -> {
if (!shouldRotate(animation)) {
- Log.v(TAG, "skip rotating udfps location ROTATION_90")
+ Log.v(TAG, "skip rotating udfps location ROTATION_90" +
+ " animation=$animation" +
+ " isGoingToSleep=${keyguardUpdateMonitor.isGoingToSleep}" +
+ " isOccluded=${keyguardStateController.isOccluded}")
} else {
Log.v(TAG, "rotate udfps location ROTATION_90")
x = (location.sensorLocationY - location.sensorRadius - paddingX)
@@ -309,7 +312,10 @@ class UdfpsControllerOverlay(
}
Surface.ROTATION_270 -> {
if (!shouldRotate(animation)) {
- Log.v(TAG, "skip rotating udfps location ROTATION_270")
+ Log.v(TAG, "skip rotating udfps location ROTATION_270" +
+ " animation=$animation" +
+ " isGoingToSleep=${keyguardUpdateMonitor.isGoingToSleep}" +
+ " isOccluded=${keyguardStateController.isOccluded}")
} else {
Log.v(TAG, "rotate udfps location ROTATION_270")
x = (p.x - location.sensorLocationY - location.sensorRadius - paddingX)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
deleted file mode 100644
index 6989547dce52..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
-import static android.hardware.biometrics.BiometricManager.Authenticators;
-import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.hardware.biometrics.PromptInfo;
-import android.hardware.biometrics.SensorPropertiesInternal;
-import android.os.UserManager;
-import android.util.DisplayMetrics;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-
-import com.android.internal.widget.LockPatternUtils;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-
-public class Utils {
-
- public static final int CREDENTIAL_PIN = 1;
- public static final int CREDENTIAL_PATTERN = 2;
- public static final int CREDENTIAL_PASSWORD = 3;
-
- /** Base set of layout flags for fingerprint overlay widgets. */
- public static final int FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS =
- WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD})
- @interface CredentialType {}
-
- static float dpToPixels(Context context, float dp) {
- return dp * ((float) context.getResources().getDisplayMetrics().densityDpi
- / DisplayMetrics.DENSITY_DEFAULT);
- }
-
- static void notifyAccessibilityContentChanged(AccessibilityManager am, ViewGroup view) {
- if (!am.isEnabled()) {
- return;
- }
- AccessibilityEvent event = AccessibilityEvent.obtain();
- event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- event.setContentChangeTypes(CONTENT_CHANGE_TYPE_SUBTREE);
- view.sendAccessibilityEventUnchecked(event);
- view.notifySubtreeAccessibilityStateChanged(view, view, CONTENT_CHANGE_TYPE_SUBTREE);
- }
-
- static boolean isDeviceCredentialAllowed(PromptInfo promptInfo) {
- @Authenticators.Types final int authenticators = promptInfo.getAuthenticators();
- return (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0;
- }
-
- static boolean isBiometricAllowed(PromptInfo promptInfo) {
- @Authenticators.Types final int authenticators = promptInfo.getAuthenticators();
- return (authenticators & Authenticators.BIOMETRIC_WEAK) != 0;
- }
-
- static @CredentialType int getCredentialType(Context context, int userId) {
- final LockPatternUtils lpu = new LockPatternUtils(context);
- switch (lpu.getKeyguardStoredPasswordQuality(userId)) {
- case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
- return CREDENTIAL_PATTERN;
- case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
- case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
- return CREDENTIAL_PIN;
- case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
- case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
- case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
- case DevicePolicyManager.PASSWORD_QUALITY_MANAGED:
- return CREDENTIAL_PASSWORD;
- default:
- return CREDENTIAL_PASSWORD;
- }
- }
-
- static boolean isManagedProfile(Context context, int userId) {
- final UserManager userManager = context.getSystemService(UserManager.class);
- return userManager.isManagedProfile(userId);
- }
-
- static boolean containsSensorId(@Nullable List<? extends SensorPropertiesInternal> properties,
- int sensorId) {
- if (properties == null) {
- return false;
- }
-
- for (SensorPropertiesInternal prop : properties) {
- if (prop.sensorId == sensorId) {
- return true;
- }
- }
-
- return false;
- }
-
- static boolean isSystem(@NonNull Context context, @Nullable String clientPackage) {
- final boolean hasPermission = context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
- == PackageManager.PERMISSION_GRANTED;
- return hasPermission && "android".equals(clientPackage);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
new file mode 100644
index 000000000000..d0d6f4cbf166
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.biometrics
+
+import android.Manifest
+import android.annotation.IntDef
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+import android.content.Context
+import android.content.pm.PackageManager
+import android.hardware.biometrics.BiometricManager.Authenticators
+import android.hardware.biometrics.PromptInfo
+import android.hardware.biometrics.SensorPropertiesInternal
+import android.os.UserManager
+import android.util.DisplayMetrics
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityManager
+import com.android.internal.widget.LockPatternUtils
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+
+object Utils {
+ const val CREDENTIAL_PIN = 1
+ const val CREDENTIAL_PATTERN = 2
+ const val CREDENTIAL_PASSWORD = 3
+
+ /** Base set of layout flags for fingerprint overlay widgets. */
+ const val FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS =
+ (WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
+
+ @JvmStatic
+ fun dpToPixels(context: Context, dp: Float): Float {
+ val density = context.resources.displayMetrics.densityDpi.toFloat()
+ return dp * (density / DisplayMetrics.DENSITY_DEFAULT)
+ }
+
+ @JvmStatic
+ fun notifyAccessibilityContentChanged(am: AccessibilityManager, view: ViewGroup) {
+ if (!am.isEnabled) {
+ return
+ }
+ val event = AccessibilityEvent.obtain()
+ event.eventType = AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
+ event.contentChangeTypes =
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE
+ view.sendAccessibilityEventUnchecked(event)
+ view.notifySubtreeAccessibilityStateChanged(
+ view,
+ view,
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE
+ )
+ }
+
+ @JvmStatic
+ fun isDeviceCredentialAllowed(promptInfo: PromptInfo): Boolean =
+ (promptInfo.authenticators and Authenticators.DEVICE_CREDENTIAL) != 0
+
+ @JvmStatic
+ fun isBiometricAllowed(promptInfo: PromptInfo): Boolean =
+ (promptInfo.authenticators and Authenticators.BIOMETRIC_WEAK) != 0
+
+ @JvmStatic
+ @CredentialType
+ fun getCredentialType(utils: LockPatternUtils, userId: Int): Int =
+ when (utils.getKeyguardStoredPasswordQuality(userId)) {
+ PASSWORD_QUALITY_SOMETHING -> CREDENTIAL_PATTERN
+ PASSWORD_QUALITY_NUMERIC,
+ PASSWORD_QUALITY_NUMERIC_COMPLEX -> CREDENTIAL_PIN
+ PASSWORD_QUALITY_ALPHABETIC,
+ PASSWORD_QUALITY_ALPHANUMERIC,
+ PASSWORD_QUALITY_COMPLEX,
+ PASSWORD_QUALITY_MANAGED -> CREDENTIAL_PASSWORD
+ else -> CREDENTIAL_PASSWORD
+ }
+
+ @JvmStatic
+ fun isManagedProfile(context: Context, userId: Int): Boolean =
+ context.getSystemService(UserManager::class.java)?.isManagedProfile(userId) ?: false
+
+ @JvmStatic
+ fun <T : SensorPropertiesInternal> findFirstSensorProperties(
+ properties: List<T>?,
+ sensorIds: IntArray
+ ): T? = properties?.firstOrNull { sensorIds.contains(it.sensorId) }
+
+ @JvmStatic
+ fun isSystem(context: Context, clientPackage: String?): Boolean {
+ val hasPermission =
+ (context.checkCallingOrSelfPermission(Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ == PackageManager.PERMISSION_GRANTED)
+ return hasPermission && "android" == clientPackage
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD)
+ internal annotation class CredentialType
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index 4819bf565cbc..a4f9f3a9bc08 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -54,7 +54,7 @@ import com.android.systemui.animation.Interpolators
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.util.concurrency.DelayableExecutor
-import kotlin.reflect.KClass
+import java.util.function.Supplier
/**
* Wraps the widgets that make up the UI representation of a {@link Control}. Updates to the view
@@ -90,20 +90,20 @@ class ControlViewHolder(
status: Int,
template: ControlTemplate,
deviceType: Int
- ): KClass<out Behavior> {
+ ): Supplier<out Behavior> {
return when {
- status != Control.STATUS_OK -> StatusBehavior::class
- template == ControlTemplate.NO_TEMPLATE -> TouchBehavior::class
- template is ThumbnailTemplate -> ThumbnailBehavior::class
+ status != Control.STATUS_OK -> Supplier { StatusBehavior() }
+ template == ControlTemplate.NO_TEMPLATE -> Supplier { TouchBehavior() }
+ template is ThumbnailTemplate -> Supplier { ThumbnailBehavior() }
// Required for legacy support, or where cameras do not use the new template
- deviceType == DeviceTypes.TYPE_CAMERA -> TouchBehavior::class
- template is ToggleTemplate -> ToggleBehavior::class
- template is StatelessTemplate -> TouchBehavior::class
- template is ToggleRangeTemplate -> ToggleRangeBehavior::class
- template is RangeTemplate -> ToggleRangeBehavior::class
- template is TemperatureControlTemplate -> TemperatureControlBehavior::class
- else -> DefaultBehavior::class
+ deviceType == DeviceTypes.TYPE_CAMERA -> Supplier { TouchBehavior() }
+ template is ToggleTemplate -> Supplier { ToggleBehavior() }
+ template is StatelessTemplate -> Supplier { TouchBehavior() }
+ template is ToggleRangeTemplate -> Supplier { ToggleRangeBehavior() }
+ template is RangeTemplate -> Supplier { ToggleRangeBehavior() }
+ template is TemperatureControlTemplate -> Supplier { TemperatureControlBehavior() }
+ else -> Supplier { DefaultBehavior() }
}
}
}
@@ -253,13 +253,14 @@ class ControlViewHolder(
fun bindBehavior(
existingBehavior: Behavior?,
- clazz: KClass<out Behavior>,
+ supplier: Supplier<out Behavior>,
offset: Int = 0
): Behavior {
- val behavior = if (existingBehavior == null || existingBehavior!!::class != clazz) {
+ val newBehavior = supplier.get()
+ val behavior = if (existingBehavior == null ||
+ existingBehavior::class != newBehavior::class) {
// Behavior changes can signal a change in template from the app or
// first time setup
- val newBehavior = clazz.java.newInstance()
newBehavior.initialize(this)
// let behaviors define their own, if necessary, and clear any existing ones
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 7e1fce298fbd..ebc766635733 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -19,6 +19,8 @@ package com.android.systemui.dreams;
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -177,9 +179,26 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
}
mDreamOverlayContainerViewController.init();
+ // Make extra sure the container view has been removed from its old parent (otherwise we
+ // risk an IllegalStateException in some cases when setting the container view as the
+ // window's content view and the container view hasn't been properly removed previously).
+ removeContainerViewFromParent();
mWindow.setContentView(mDreamOverlayContainerViewController.getContainerView());
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
windowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
}
+
+ private void removeContainerViewFromParent() {
+ View containerView = mDreamOverlayContainerViewController.getContainerView();
+ if (containerView == null) {
+ return;
+ }
+ ViewGroup parentView = (ViewGroup) containerView.getParent();
+ if (parentView == null) {
+ return;
+ }
+ Log.w(TAG, "Removing dream overlay container view parent!");
+ parentView.removeView(containerView);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 582965a12528..35f29b94966f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -23,6 +23,7 @@ import android.content.Context
import android.graphics.Matrix
import android.graphics.Rect
import android.os.Handler
+import android.util.Log
import android.view.RemoteAnimationTarget
import android.view.SyncRtSurfaceTransactionApplier
import android.view.View
@@ -47,6 +48,8 @@ import dagger.Lazy
import javax.inject.Inject
import kotlin.math.min
+const val TAG = "KeyguardUnlock"
+
/**
* Starting scale factor for the app/launcher surface behind the keyguard, when it's animating
* in during keyguard exit.
@@ -584,8 +587,16 @@ class KeyguardUnlockAnimationController @Inject constructor(
* animation.
*/
fun hideKeyguardViewAfterRemoteAnimation() {
- // Hide the keyguard, with no fade out since we animated it away during the unlock.
- keyguardViewController.hide(surfaceBehindRemoteAnimationStartTime, 0 /* fadeOutDuration */)
+ if (keyguardViewController.isShowing) {
+ // Hide the keyguard, with no fade out since we animated it away during the unlock.
+ keyguardViewController.hide(
+ surfaceBehindRemoteAnimationStartTime,
+ 0 /* fadeOutDuration */
+ )
+ } else {
+ Log.e(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
+ "showing. Ignoring...")
+ }
}
private fun applyParamsToSurface(params: SyncRtSurfaceTransactionApplier.SurfaceParams) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 3cd390533132..0b23ad50a726 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -54,6 +54,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
MediaOutputDialog mediaOutputDialog) {
super(controller);
mMediaOutputDialog = mediaOutputDialog;
+ setHasStableIds(true);
}
@Override
@@ -79,6 +80,20 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
}
@Override
+ public long getItemId(int position) {
+ final int size = mController.getMediaDevices().size();
+ if (position == size && mController.isZeroMode()) {
+ return -1;
+ } else if (position < size) {
+ return ((List<MediaDevice>) (mController.getMediaDevices()))
+ .get(position).getId().hashCode();
+ } else if (DEBUG) {
+ Log.d(TAG, "Incorrect position for item id: " + position);
+ }
+ return position;
+ }
+
+ @Override
public int getItemCount() {
if (mController.isZeroMode()) {
// Add extra one for "pair new" or dynamic group
@@ -159,7 +174,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
onCheckBoxClicked(false, device);
});
setCheckBoxColor(mCheckBox, mController.getColorActiveItem());
- initSessionSeekbar();
+ initSeekbar(device);
} else if (!mController.hasAdjustVolumeUserRestriction() && currentlyConnected) {
mStatusIcon.setImageDrawable(
mContext.getDrawable(R.drawable.media_output_status_check));
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 1f11d0c62bd7..c96aca37987b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -249,7 +249,7 @@ public abstract class MediaOutputBaseAdapter extends
mSeekBar.setMin(0);
final int currentVolume = device.getCurrentVolume();
if (mSeekBar.getProgress() != currentVolume) {
- mSeekBar.setProgress(currentVolume);
+ mSeekBar.setProgress(currentVolume, true);
}
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
@@ -278,7 +278,7 @@ public abstract class MediaOutputBaseAdapter extends
mSeekBar.setMin(0);
final int currentVolume = mController.getSessionVolume();
if (mSeekBar.getProgress() != currentVolume) {
- mSeekBar.setProgress(currentVolume);
+ mSeekBar.setProgress(currentVolume, true);
}
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 9ea27634df6a..4dacf5dfd78c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -263,6 +263,15 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
// Notify FalsingManager that an intentional gesture has occurred.
// TODO(b/186519446): use a different method than isFalseTouch
mFalsingManager.isFalseTouch(BACK_GESTURE);
+ // Only inject back keycodes when ahead-of-time back dispatching is disabled.
+ if (mBackAnimation == null) {
+ boolean sendDown = sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
+ boolean sendUp = sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
+ if (DEBUG_MISSING_GESTURE) {
+ Log.d(DEBUG_MISSING_GESTURE_TAG, "Triggered back: down="
+ + sendDown + ", up=" + sendUp);
+ }
+ }
mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x,
(int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
@@ -936,6 +945,9 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
public void setBackAnimation(BackAnimation backAnimation) {
mBackAnimation = backAnimation;
+ if (mEdgeBackPlugin != null && mEdgeBackPlugin instanceof NavigationBarEdgePanel) {
+ ((NavigationBarEdgePanel) mEdgeBackPlugin).setBackAnimation(backAnimation);
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index a6bad15e0865..a6919e826d4f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -280,7 +280,7 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
}
};
private BackCallback mBackCallback;
- private final BackAnimation mBackAnimation;
+ private BackAnimation mBackAnimation;
public NavigationBarEdgePanel(Context context,
BackAnimation backAnimation) {
@@ -385,6 +385,10 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
mShowProtection = !isPrimaryDisplay;
}
+ public void setBackAnimation(BackAnimation backAnimation) {
+ mBackAnimation = backAnimation;
+ }
+
@Override
public void onDestroy() {
cancelFailsafe();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 8a02e5952659..5932a64c1c71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -310,11 +310,11 @@ public class CommandQueue extends IStatusBar.Stub implements
long requestId, @BiometricMultiSensorMode int multiSensorConfig) {
}
- /** @see IStatusBar#onBiometricAuthenticated() */
- default void onBiometricAuthenticated() {
+ /** @see IStatusBar#onBiometricAuthenticated(int) */
+ default void onBiometricAuthenticated(@Modality int modality) {
}
- /** @see IStatusBar#onBiometricHelp(String) */
+ /** @see IStatusBar#onBiometricHelp(int, String) */
default void onBiometricHelp(@Modality int modality, String message) {
}
@@ -963,9 +963,11 @@ public class CommandQueue extends IStatusBar.Stub implements
}
@Override
- public void onBiometricAuthenticated() {
+ public void onBiometricAuthenticated(@Modality int modality) {
synchronized (mLock) {
- mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget();
+ SomeArgs args = SomeArgs.obtain();
+ args.argi1 = modality;
+ mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, args).sendToTarget();
}
}
@@ -1465,9 +1467,11 @@ public class CommandQueue extends IStatusBar.Stub implements
break;
}
case MSG_BIOMETRIC_AUTHENTICATED: {
+ SomeArgs someArgs = (SomeArgs) msg.obj;
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).onBiometricAuthenticated();
+ mCallbacks.get(i).onBiometricAuthenticated(someArgs.argi1 /* modality */);
}
+ someArgs.recycle();
break;
}
case MSG_BIOMETRIC_HELP: {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 0509a7caa719..ccec0c2d58cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -50,6 +50,7 @@ import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.BatteryManager;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -122,7 +123,7 @@ public class KeyguardIndicationController {
private final Context mContext;
private final BroadcastDispatcher mBroadcastDispatcher;
private final KeyguardStateController mKeyguardStateController;
- private final StatusBarStateController mStatusBarStateController;
+ protected final StatusBarStateController mStatusBarStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private ViewGroup mIndicationArea;
private KeyguardIndicationTextView mTopIndicationView;
@@ -138,6 +139,7 @@ public class KeyguardIndicationController {
private final IActivityManager mIActivityManager;
private final FalsingManager mFalsingManager;
private final KeyguardBypassController mKeyguardBypassController;
+ private final Handler mHandler;
protected KeyguardIndicationRotateTextViewController mRotateTextViewController;
private BroadcastReceiver mBroadcastReceiver;
@@ -194,7 +196,9 @@ public class KeyguardIndicationController {
* Creates a new KeyguardIndicationController and registers callbacks.
*/
@Inject
- public KeyguardIndicationController(Context context,
+ public KeyguardIndicationController(
+ Context context,
+ @Main Looper mainLooper,
WakeLock.Builder wakeLockBuilder,
KeyguardStateController keyguardStateController,
StatusBarStateController statusBarStateController,
@@ -230,6 +234,19 @@ public class KeyguardIndicationController {
mKeyguardBypassController = keyguardBypassController;
mScreenLifecycle = screenLifecycle;
mScreenLifecycle.addObserver(mScreenObserver);
+
+ mHandler = new Handler(mainLooper) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_HIDE_TRANSIENT) {
+ hideTransientIndication();
+ } else if (msg.what == MSG_SHOW_ACTION_TO_UNLOCK) {
+ showActionToUnlock();
+ } else if (msg.what == MSG_HIDE_BIOMETRIC_MESSAGE) {
+ hideBiometricMessage();
+ }
+ }
+ };
}
/** Call this after construction to finish setting up the instance. */
@@ -242,7 +259,6 @@ public class KeyguardIndicationController {
mDockManager.addAlignmentStateListener(
alignState -> mHandler.post(() -> handleAlignStateChanged(alignState)));
mKeyguardUpdateMonitor.registerCallback(getKeyguardCallback());
- mKeyguardUpdateMonitor.registerCallback(mTickReceiver);
mStatusBarStateController.addCallback(mStatusBarStateListener);
mKeyguardStateController.addCallback(mKeyguardStateCallback);
@@ -260,7 +276,7 @@ public class KeyguardIndicationController {
mLockScreenIndicationView,
mExecutor,
mStatusBarStateController);
- updateIndication(false /* animate */);
+ updateDeviceEntryIndication(false /* animate */);
updateOrganizedOwnedDevice();
if (mBroadcastReceiver == null) {
// Update the disclosure proactively to avoid IPC on the critical path.
@@ -288,7 +304,7 @@ public class KeyguardIndicationController {
}
if (!alignmentIndication.equals(mAlignmentIndication)) {
mAlignmentIndication = alignmentIndication;
- updateIndication(false);
+ updateDeviceEntryIndication(false);
}
}
@@ -309,28 +325,30 @@ public class KeyguardIndicationController {
return mUpdateMonitorCallback;
}
- /**
- * This method also doesn't update transient messages like biometrics since those messages
- * are also updated separately.
- */
- private void updatePersistentIndications(boolean animate, int userId) {
- updateDisclosure();
- updateOwnerInfo();
- updateBattery(animate);
- updateUserLocked(userId);
- updateTrust(userId, getTrustGrantedIndication(), getTrustManagedIndication());
- updateAlignment();
- updateLogoutView();
- updateResting();
+ private void updateLockScreenIndications(boolean animate, int userId) {
+ // update transient messages:
+ updateBiometricMessage();
+ updateTransient();
+
+ // Update persistent messages. The following methods should only be called if we're on the
+ // lock screen:
+ updateLockScreenDisclosureMsg();
+ updateLockScreenOwnerInfo();
+ updateLockScreenBatteryMsg(animate);
+ updateLockScreenUserLockedMsg(userId);
+ updateLockScreenTrustMsg(userId, getTrustGrantedIndication(), getTrustManagedIndication());
+ updateLockScreenAlignmentMsg();
+ updateLockScreenLogoutView();
+ updateLockScreenRestingMsg();
}
private void updateOrganizedOwnedDevice() {
// avoid calling this method since it has an IPC
mOrganizationOwnedDevice = whitelistIpcs(this::isOrganizationOwnedDevice);
- updatePersistentIndications(false, KeyguardUpdateMonitor.getCurrentUser());
+ updateDeviceEntryIndication(false);
}
- private void updateDisclosure() {
+ private void updateLockScreenDisclosureMsg() {
if (mOrganizationOwnedDevice) {
mBackgroundExecutor.execute(() -> {
final CharSequence organizationName = getOrganizationOwnedDeviceOrganizationName();
@@ -374,7 +392,7 @@ public class KeyguardIndicationController {
}
}
- private void updateOwnerInfo() {
+ private void updateLockScreenOwnerInfo() {
// Check device owner info on a bg thread.
// It makes multiple IPCs that could block the thread it's run on.
mBackgroundExecutor.execute(() -> {
@@ -406,7 +424,7 @@ public class KeyguardIndicationController {
});
}
- private void updateBattery(boolean animate) {
+ private void updateLockScreenBatteryMsg(boolean animate) {
if (mPowerPluggedIn || mEnableBatteryDefender) {
String powerIndication = computePowerIndication();
if (DEBUG_CHARGING_SPEED) {
@@ -426,7 +444,7 @@ public class KeyguardIndicationController {
}
}
- private void updateUserLocked(int userId) {
+ private void updateLockScreenUserLockedMsg(int userId) {
if (!mKeyguardUpdateMonitor.isUserUnlocked(userId)) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_USER_LOCKED,
@@ -442,6 +460,11 @@ public class KeyguardIndicationController {
}
private void updateBiometricMessage() {
+ if (mDozing) {
+ updateDeviceEntryIndication(false);
+ return;
+ }
+
if (!TextUtils.isEmpty(mBiometricMessage)) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_BIOMETRIC_MESSAGE,
@@ -455,25 +478,22 @@ public class KeyguardIndicationController {
} else {
mRotateTextViewController.hideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE);
}
+ }
+ private void updateTransient() {
if (mDozing) {
- updateIndication(false);
+ updateDeviceEntryIndication(false);
+ return;
}
- }
- private void updateTransient() {
if (!TextUtils.isEmpty(mTransientIndication)) {
mRotateTextViewController.showTransient(mTransientIndication);
} else {
mRotateTextViewController.hideTransient();
}
-
- if (mDozing) {
- updateIndication(false);
- }
}
- private void updateTrust(int userId, CharSequence trustGrantedIndication,
+ private void updateLockScreenTrustMsg(int userId, CharSequence trustGrantedIndication,
CharSequence trustManagedIndication) {
if (!TextUtils.isEmpty(trustGrantedIndication)
&& mKeyguardUpdateMonitor.getUserHasTrust(userId)) {
@@ -499,7 +519,7 @@ public class KeyguardIndicationController {
}
}
- private void updateAlignment() {
+ private void updateLockScreenAlignmentMsg() {
if (!TextUtils.isEmpty(mAlignmentIndication)) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_ALIGNMENT,
@@ -514,7 +534,7 @@ public class KeyguardIndicationController {
}
}
- private void updateResting() {
+ private void updateLockScreenRestingMsg() {
if (!TextUtils.isEmpty(mRestingIndication)
&& !mRotateTextViewController.hasIndications()) {
mRotateTextViewController.updateIndication(
@@ -529,7 +549,7 @@ public class KeyguardIndicationController {
}
}
- private void updateLogoutView() {
+ private void updateLockScreenLogoutView() {
final boolean shouldShowLogout = mKeyguardUpdateMonitor.isLogoutEnabled()
&& KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
if (shouldShowLogout) {
@@ -608,7 +628,7 @@ public class KeyguardIndicationController {
if (!mHandler.hasMessages(MSG_HIDE_TRANSIENT)) {
hideTransientIndication();
}
- updateIndication(false);
+ updateDeviceEntryIndication(false);
} else if (!visible) {
// If we unlock and return to keyguard quickly, previous error should not be shown
hideTransientIndication();
@@ -620,7 +640,7 @@ public class KeyguardIndicationController {
*/
public void setRestingIndication(String restingIndication) {
mRestingIndication = restingIndication;
- updateIndication(false);
+ updateDeviceEntryIndication(false);
}
/**
@@ -697,6 +717,10 @@ public class KeyguardIndicationController {
* Shows {@param biometricMessage} until it is hidden by {@link #hideBiometricMessage}.
*/
private void showBiometricMessage(CharSequence biometricMessage) {
+ if (TextUtils.equals(biometricMessage, mBiometricMessage)) {
+ return;
+ }
+
mBiometricMessage = biometricMessage;
mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK);
@@ -725,7 +749,12 @@ public class KeyguardIndicationController {
}
}
- protected final void updateIndication(boolean animate) {
+ /**
+ * Updates message shown to the user. If the device is dozing, a single message with the highest
+ * precedence is shown. If the device is not dozing (on the lock screen), then several messages
+ * may continuously be cycled through.
+ */
+ protected final void updateDeviceEntryIndication(boolean animate) {
if (!mVisible) {
return;
}
@@ -734,44 +763,37 @@ public class KeyguardIndicationController {
mIndicationArea.setVisibility(VISIBLE);
// Walk down a precedence-ordered list of what indication
- // should be shown based on user or device state
- // AoD
+ // should be shown based on device state
if (mDozing) {
mLockScreenIndicationView.setVisibility(View.GONE);
mTopIndicationView.setVisibility(VISIBLE);
// When dozing we ignore any text color and use white instead, because
// colors can be hard to read in low brightness.
mTopIndicationView.setTextColor(Color.WHITE);
+
+ CharSequence newIndication = null;
if (!TextUtils.isEmpty(mBiometricMessage)) {
- mWakeLock.setAcquired(true);
- mTopIndicationView.switchIndication(mBiometricMessage, null,
- true, () -> mWakeLock.setAcquired(false));
+ newIndication = mBiometricMessage;
} else if (!TextUtils.isEmpty(mTransientIndication)) {
- mWakeLock.setAcquired(true);
- mTopIndicationView.switchIndication(mTransientIndication, null,
- true, () -> mWakeLock.setAcquired(false));
+ newIndication = mTransientIndication;
} else if (!mBatteryPresent) {
// If there is no battery detected, hide the indication and bail
mIndicationArea.setVisibility(GONE);
+ return;
} else if (!TextUtils.isEmpty(mAlignmentIndication)) {
- mTopIndicationView.switchIndication(mAlignmentIndication, null,
- false /* animate */, null /* onAnimationEndCallback */);
+ newIndication = mAlignmentIndication;
mTopIndicationView.setTextColor(mContext.getColor(R.color.misalignment_text_color));
} else if (mPowerPluggedIn || mEnableBatteryDefender) {
- String indication = computePowerIndication();
- if (animate) {
- mWakeLock.setAcquired(true);
- mTopIndicationView.switchIndication(indication, null, true /* animate */,
- () -> mWakeLock.setAcquired(false));
- } else {
- mTopIndicationView.switchIndication(indication, null, false /* animate */,
- null /* onAnimationEndCallback */);
- }
+ newIndication = computePowerIndication();
} else {
- String percentage = NumberFormat.getPercentInstance()
+ newIndication = NumberFormat.getPercentInstance()
.format(mBatteryLevel / 100f);
- mTopIndicationView.switchIndication(percentage, null /* indication */,
- false /* animate */, null /* onAnimationEnd*/);
+ }
+
+ if (!TextUtils.equals(mTopIndicationView.getText(), newIndication)) {
+ mWakeLock.setAcquired(true);
+ mTopIndicationView.switchIndication(newIndication, null,
+ true, () -> mWakeLock.setAcquired(false));
}
return;
}
@@ -780,7 +802,7 @@ public class KeyguardIndicationController {
mTopIndicationView.setVisibility(GONE);
mTopIndicationView.setText(null);
mLockScreenIndicationView.setVisibility(View.VISIBLE);
- updatePersistentIndications(animate, KeyguardUpdateMonitor.getCurrentUser());
+ updateLockScreenIndications(animate, KeyguardUpdateMonitor.getCurrentUser());
}
protected String computePowerIndication() {
@@ -842,29 +864,6 @@ public class KeyguardIndicationController {
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
}
- private final KeyguardUpdateMonitorCallback mTickReceiver =
- new KeyguardUpdateMonitorCallback() {
- @Override
- public void onTimeChanged() {
- if (mVisible) {
- updateIndication(false /* animate */);
- }
- }
- };
-
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == MSG_HIDE_TRANSIENT) {
- hideTransientIndication();
- } else if (msg.what == MSG_SHOW_ACTION_TO_UNLOCK) {
- showActionToUnlock();
- } else if (msg.what == MSG_HIDE_BIOMETRIC_MESSAGE) {
- hideBiometricMessage();
- }
- }
- };
-
/**
* Show message on the keyguard for how the user can unlock/enter their device.
*/
@@ -929,7 +928,7 @@ public class KeyguardIndicationController {
pw.println(" mBiometricMessage: " + mBiometricMessage);
pw.println(" mBatteryLevel: " + mBatteryLevel);
pw.println(" mBatteryPresent: " + mBatteryPresent);
- pw.println(" mTextView.getText(): " + (
+ pw.println(" AOD text: " + (
mTopIndicationView == null ? null : mTopIndicationView.getText()));
pw.println(" computePowerIndication(): " + computePowerIndication());
pw.println(" trustGrantedIndication: " + getTrustGrantedIndication());
@@ -940,6 +939,13 @@ public class KeyguardIndicationController {
public static final int HIDE_DELAY_MS = 5000;
@Override
+ public void onTimeChanged() {
+ if (mVisible) {
+ updateDeviceEntryIndication(false /* animate */);
+ }
+ }
+
+ @Override
public void onRefreshBatteryInfo(BatteryStatus status) {
boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING
|| status.isCharged();
@@ -962,7 +968,7 @@ public class KeyguardIndicationController {
Log.e(TAG, "Error calling IBatteryStats: ", e);
mChargingTimeRemaining = -1;
}
- updateIndication(!wasPluggedIn && mPowerPluggedInWired);
+ updateDeviceEntryIndication(!wasPluggedIn && mPowerPluggedInWired);
if (mDozing) {
if (!wasPluggedIn && mPowerPluggedIn) {
showTransientIndication(computePowerIndication());
@@ -1084,14 +1090,13 @@ public class KeyguardIndicationController {
if (KeyguardUpdateMonitor.getCurrentUser() != userId) {
return;
}
- updateTrust(userId, getTrustGrantedIndication(), getTrustManagedIndication());
+ updateDeviceEntryIndication(false);
}
@Override
public void showTrustGrantedMessage(CharSequence message) {
mTrustGrantedIndication = message;
- updateTrust(KeyguardUpdateMonitor.getCurrentUser(), getTrustGrantedIndication(),
- getTrustManagedIndication());
+ updateDeviceEntryIndication(false);
}
@Override
@@ -1125,21 +1130,21 @@ public class KeyguardIndicationController {
@Override
public void onUserSwitchComplete(int userId) {
if (mVisible) {
- updateIndication(false);
+ updateDeviceEntryIndication(false);
}
}
@Override
public void onUserUnlocked() {
if (mVisible) {
- updateIndication(false);
+ updateDeviceEntryIndication(false);
}
}
@Override
public void onLogoutEnabledChanged() {
if (mVisible) {
- updateIndication(false);
+ updateDeviceEntryIndication(false);
}
}
@@ -1167,7 +1172,7 @@ public class KeyguardIndicationController {
if (mDozing) {
hideBiometricMessage();
}
- updateIndication(false);
+ updateDeviceEntryIndication(false);
}
};
@@ -1175,7 +1180,7 @@ public class KeyguardIndicationController {
new KeyguardStateController.Callback() {
@Override
public void onUnlockedChanged() {
- updateIndication(false);
+ updateDeviceEntryIndication(false);
}
@Override
@@ -1185,7 +1190,7 @@ public class KeyguardIndicationController {
mTopIndicationView.clearMessages();
mRotateTextViewController.clearMessages();
} else {
- updatePersistentIndications(false, KeyguardUpdateMonitor.getCurrentUser());
+ updateDeviceEntryIndication(false);
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index ebe91c7dc5d0..8b25c2bc20b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -292,20 +292,11 @@ public class DozeParameters implements
}
public void updateControlScreenOff() {
- final boolean controlScreenOff = shouldControlUnlockedScreenOff()
- || (!getDisplayNeedsBlanking() && getAlwaysOn() && mKeyguardShowing);
- setControlScreenOffAnimation(controlScreenOff);
- }
-
- /**
- * Whether we're capable of controlling the screen off animation if we want to. This isn't
- * possible if AOD isn't even enabled or if the flag is disabled, or if the display needs
- * blanking.
- */
- public boolean canControlUnlockedScreenOff() {
- return getAlwaysOn()
- && mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)
- && !getDisplayNeedsBlanking();
+ if (!getDisplayNeedsBlanking()) {
+ final boolean controlScreenOff =
+ getAlwaysOn() && (mKeyguardShowing || shouldControlUnlockedScreenOff());
+ setControlScreenOffAnimation(controlScreenOff);
+ }
}
/**
@@ -318,7 +309,8 @@ public class DozeParameters implements
* disabled for a11y.
*/
public boolean shouldControlUnlockedScreenOff() {
- return mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation();
+ return canControlUnlockedScreenOff()
+ && mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation();
}
public boolean shouldDelayKeyguardShow() {
@@ -350,6 +342,16 @@ public class DozeParameters implements
return getAlwaysOn() && mKeyguardShowing;
}
+ /**
+ * Whether we're capable of controlling the screen off animation if we want to. This isn't
+ * possible if AOD isn't even enabled or if the flag is disabled.
+ */
+ public boolean canControlUnlockedScreenOff() {
+ return getAlwaysOn()
+ && mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)
+ && !getDisplayNeedsBlanking();
+ }
+
private boolean getBoolean(String propName, int resId) {
return SystemProperties.getBoolean(propName, mResources.getBoolean(resId));
}
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 8b0eaec5dcf4..c11d450e47b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -61,14 +61,6 @@ class UnlockedScreenOffAnimationController @Inject constructor(
) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
private lateinit var mCentralSurfaces: CentralSurfaces
-
- /**
- * Whether or not [initialize] has been called to provide us with the StatusBar,
- * NotificationPanelViewController, and LightRevealSrim so that we can run the unlocked screen
- * off animation.
- */
- private var initialized = false
-
private lateinit var lightRevealScrim: LightRevealScrim
private var animatorDurationScale = 1f
@@ -124,7 +116,6 @@ class UnlockedScreenOffAnimationController @Inject constructor(
centralSurfaces: CentralSurfaces,
lightRevealScrim: LightRevealScrim
) {
- this.initialized = true
this.lightRevealScrim = lightRevealScrim
this.mCentralSurfaces = centralSurfaces
@@ -271,18 +262,6 @@ class UnlockedScreenOffAnimationController @Inject constructor(
* on the current state of the device.
*/
fun shouldPlayUnlockedScreenOffAnimation(): Boolean {
- // If we haven't been initialized yet, we don't have a StatusBar/LightRevealScrim yet, so we
- // can't perform the animation.
- if (!initialized) {
- return false
- }
-
- // If the device isn't in a state where we can control unlocked screen off (no AOD enabled,
- // power save, etc.) then we shouldn't try to do so.
- if (!dozeParameters.get().canControlUnlockedScreenOff()) {
- return false
- }
-
// If we explicitly already decided not to play the screen off animation, then never change
// our mind.
if (decidedToAnimateGoingToSleep == false) {
@@ -325,7 +304,7 @@ class UnlockedScreenOffAnimationController @Inject constructor(
}
override fun shouldDelayDisplayDozeTransition(): Boolean =
- shouldPlayUnlockedScreenOffAnimation()
+ dozeParameters.get().shouldControlUnlockedScreenOff()
/**
* Whether we're doing the light reveal animation or we're done with that and animating in the
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
index 796af115bf68..58b4af43a9b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
@@ -134,6 +134,16 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase {
}
@Test
+ public void moveWindowMagnifierToPosition() throws RemoteException {
+ mIWindowMagnificationConnection.moveWindowMagnifierToPosition(TEST_DISPLAY,
+ 100f, 200f, mAnimationCallback);
+ waitForIdleSync();
+
+ verify(mWindowMagnificationController).moveWindowMagnifierToPosition(
+ eq(100f), eq(200f), any(IRemoteMagnificationAnimationCallback.class));
+ }
+
+ @Test
public void showMagnificationButton() throws RemoteException {
mIWindowMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java
new file mode 100644
index 000000000000..30bff0943da7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java
@@ -0,0 +1,54 @@
+/*
+ * 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.accessibility;
+
+import android.os.RemoteException;
+import android.view.accessibility.IRemoteMagnificationAnimationCallback;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class MockMagnificationAnimationCallback extends IRemoteMagnificationAnimationCallback.Stub {
+
+ private final CountDownLatch mCountDownLatch;
+ private final AtomicInteger mSuccessCount;
+ private final AtomicInteger mFailedCount;
+
+ MockMagnificationAnimationCallback(CountDownLatch countDownLatch) {
+ mCountDownLatch = countDownLatch;
+ mSuccessCount = new AtomicInteger();
+ mFailedCount = new AtomicInteger();
+ }
+
+ public int getSuccessCount() {
+ return mSuccessCount.get();
+ }
+
+ public int getFailedCount() {
+ return mFailedCount.get();
+ }
+
+ @Override
+ public void onResult(boolean success) throws RemoteException {
+ mCountDownLatch.countDown();
+ if (success) {
+ mSuccessCount.getAndIncrement();
+ } else {
+ mFailedCount.getAndIncrement();
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 3cc177dd8d91..21c3d6ea0660 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -60,6 +60,8 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@Ignore
@@ -218,6 +220,29 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
}
@Test
+ public void enableWindowMagnificationWithUnchanged_enabling_expectedValuesToDefault()
+ throws InterruptedException {
+ final CountDownLatch countDownLatch = new CountDownLatch(2);
+ final MockMagnificationAnimationCallback animationCallback =
+ new MockMagnificationAnimationCallback(countDownLatch);
+
+ enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ animationCallback);
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
+ Float.NaN, Float.NaN, animationCallback);
+ });
+
+ assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+ // The callback in 2nd enableWindowMagnification will return true
+ assertEquals(1, animationCallback.getSuccessCount());
+ // The callback in 1st enableWindowMagnification will return false
+ assertEquals(1, animationCallback.getFailedCount());
+ verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
+ }
+
+ @Test
public void enableWindowMagnificationWithScaleOne_enabled_AnimationAndInvokeCallback()
throws RemoteException {
enableWindowMagnificationWithoutAnimation();
@@ -425,6 +450,102 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
}
@Test
+ public void moveWindowMagnifierToPosition_enabled_expectedValues()
+ throws InterruptedException {
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+ final MockMagnificationAnimationCallback animationCallback =
+ new MockMagnificationAnimationCallback(countDownLatch);
+ final float targetCenterX = DEFAULT_CENTER_X + 100;
+ final float targetCenterY = DEFAULT_CENTER_Y + 100;
+ enableWindowMagnificationWithoutAnimation();
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ targetCenterX, targetCenterY, animationCallback);
+ });
+
+ assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+ assertEquals(1, animationCallback.getSuccessCount());
+ assertEquals(0, animationCallback.getFailedCount());
+ verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY);
+ }
+
+ @Test
+ public void moveWindowMagnifierToPositionMultipleTimes_enabled_expectedValuesToLastOne()
+ throws InterruptedException {
+ final CountDownLatch countDownLatch = new CountDownLatch(4);
+ final MockMagnificationAnimationCallback animationCallback =
+ new MockMagnificationAnimationCallback(countDownLatch);
+ enableWindowMagnificationWithoutAnimation();
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ DEFAULT_CENTER_X + 10, DEFAULT_CENTER_Y + 10, animationCallback);
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ DEFAULT_CENTER_X + 20, DEFAULT_CENTER_Y + 20, animationCallback);
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ DEFAULT_CENTER_X + 30, DEFAULT_CENTER_Y + 30, animationCallback);
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40, animationCallback);
+ });
+
+ assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+ // only the last one callback will return true
+ assertEquals(1, animationCallback.getSuccessCount());
+ // the others will return false
+ assertEquals(3, animationCallback.getFailedCount());
+ verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40);
+ }
+
+ @Test
+ public void moveWindowMagnifierToPosition_enabling_expectedValuesToLastOne()
+ throws InterruptedException {
+ final CountDownLatch countDownLatch = new CountDownLatch(2);
+ final MockMagnificationAnimationCallback animationCallback =
+ new MockMagnificationAnimationCallback(countDownLatch);
+ final float targetCenterX = DEFAULT_CENTER_X + 100;
+ final float targetCenterY = DEFAULT_CENTER_Y + 100;
+
+ enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ animationCallback);
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ targetCenterX, targetCenterY, animationCallback);
+ });
+
+ assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+ // The callback in moveWindowMagnifierToPosition will return true
+ assertEquals(1, animationCallback.getSuccessCount());
+ // The callback in enableWindowMagnification will return false
+ assertEquals(1, animationCallback.getFailedCount());
+ verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY);
+ }
+
+ @Test
+ public void moveWindowMagnifierToPositionWithCenterUnchanged_enabling_expectedValuesToDefault()
+ throws InterruptedException {
+ final CountDownLatch countDownLatch = new CountDownLatch(2);
+ final MockMagnificationAnimationCallback animationCallback =
+ new MockMagnificationAnimationCallback(countDownLatch);
+
+ enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ animationCallback);
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ Float.NaN, Float.NaN, animationCallback);
+ });
+
+ assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+ // The callback in moveWindowMagnifierToPosition will return true
+ assertEquals(1, animationCallback.getSuccessCount());
+ // The callback in enableWindowMagnification will return false
+ assertEquals(1, animationCallback.getFailedCount());
+ verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
+ }
+
+ @Test
public void enableWindowMagnificationWithSameScale_enabled_doNothingButInvokeCallback()
throws RemoteException {
enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
@@ -569,6 +690,20 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + 100f, DEFAULT_CENTER_Y + 100f);
}
+ @Test
+ public void moveWindowMagnifierToPosition_enabled() {
+ final float targetCenterX = DEFAULT_CENTER_X + 100;
+ final float targetCenterY = DEFAULT_CENTER_Y + 100;
+ enableWindowMagnificationWithoutAnimation();
+
+ mInstrumentation.runOnMainSync(
+ () -> mController.moveWindowMagnifierToPosition(targetCenterX, targetCenterY,
+ mAnimationCallback));
+ SystemClock.sleep(mWaitingAnimationPeriod);
+
+ verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY);
+ }
+
private void verifyFinalSpec(float expectedScale, float expectedCenterX,
float expectedCenterY) {
assertEquals(expectedScale, mController.getScale(), 0f);
@@ -663,6 +798,13 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
}
@Override
+ void moveWindowMagnifierToPosition(float positionX, float positionY,
+ IRemoteMagnificationAnimationCallback callback) {
+ super.moveWindowMagnifierToPosition(positionX, positionY, callback);
+ mSpyController.moveWindowMagnifierToPosition(positionX, positionY, callback);
+ }
+
+ @Override
void setScale(float scale) {
super.setScale(scale);
mSpyController.setScale(scale);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 6e5926db519d..a49c4d76445b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -40,6 +40,7 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -88,6 +89,8 @@ import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@LargeTest
@@ -96,12 +99,16 @@ import java.util.concurrent.atomic.AtomicInteger;
public class WindowMagnificationControllerTest extends SysuiTestCase {
private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000;
+ private static final long ANIMATION_DURATION_MS = 300;
+ private final long mWaitingAnimationPeriod = 2 * ANIMATION_DURATION_MS;
@Mock
private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
@Mock
private MirrorWindowControl mMirrorWindowControl;
@Mock
private WindowMagnifierCallback mWindowMagnifierCallback;
+ @Mock
+ IRemoteMagnificationAnimationCallback mAnimationCallback;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
@@ -287,6 +294,82 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
}
@Test
+ public void moveWindowMagnifierToPositionWithAnimation_expectedValuesAndInvokeCallback()
+ throws InterruptedException {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+ Float.NaN, 0, 0, null);
+ });
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+ final MockMagnificationAnimationCallback animationCallback =
+ new MockMagnificationAnimationCallback(countDownLatch);
+ final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
+ verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
+ .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
+ final float targetCenterX = sourceBoundsCaptor.getValue().exactCenterX() + 10;
+ final float targetCenterY = sourceBoundsCaptor.getValue().exactCenterY() + 10;
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.moveWindowMagnifierToPosition(
+ targetCenterX, targetCenterY, animationCallback);
+ });
+
+ assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+ assertEquals(1, animationCallback.getSuccessCount());
+ assertEquals(0, animationCallback.getFailedCount());
+ verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
+ .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
+ assertEquals(mWindowMagnificationController.getCenterX(),
+ sourceBoundsCaptor.getValue().exactCenterX(), 0);
+ assertEquals(mWindowMagnificationController.getCenterY(),
+ sourceBoundsCaptor.getValue().exactCenterY(), 0);
+ assertEquals(mWindowMagnificationController.getCenterX(), targetCenterX, 0);
+ assertEquals(mWindowMagnificationController.getCenterY(), targetCenterY, 0);
+ }
+
+ @Test
+ public void moveWindowMagnifierToPositionMultipleTimes_expectedValuesAndInvokeCallback()
+ throws InterruptedException {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+ Float.NaN, 0, 0, null);
+ });
+ final CountDownLatch countDownLatch = new CountDownLatch(4);
+ final MockMagnificationAnimationCallback animationCallback =
+ new MockMagnificationAnimationCallback(countDownLatch);
+ final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
+ verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
+ .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
+ final float centerX = sourceBoundsCaptor.getValue().exactCenterX();
+ final float centerY = sourceBoundsCaptor.getValue().exactCenterY();
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.moveWindowMagnifierToPosition(
+ centerX + 10, centerY + 10, animationCallback);
+ mWindowMagnificationController.moveWindowMagnifierToPosition(
+ centerX + 20, centerY + 20, animationCallback);
+ mWindowMagnificationController.moveWindowMagnifierToPosition(
+ centerX + 30, centerY + 30, animationCallback);
+ mWindowMagnificationController.moveWindowMagnifierToPosition(
+ centerX + 40, centerY + 40, animationCallback);
+ });
+
+ assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+ // only the last one callback will return true
+ assertEquals(1, animationCallback.getSuccessCount());
+ // the others will return false
+ assertEquals(3, animationCallback.getFailedCount());
+ verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
+ .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
+ assertEquals(mWindowMagnificationController.getCenterX(),
+ sourceBoundsCaptor.getValue().exactCenterX(), 0);
+ assertEquals(mWindowMagnificationController.getCenterY(),
+ sourceBoundsCaptor.getValue().exactCenterY(), 0);
+ assertEquals(mWindowMagnificationController.getCenterX(), centerX + 40, 0);
+ assertEquals(mWindowMagnificationController.getCenterY(), centerY + 40, 0);
+ }
+
+ @Test
public void setScale_enabled_expectedValueAndUpdateStateDescription() {
mInstrumentation.runOnMainSync(
() -> mWindowMagnificationController.enableWindowMagnificationInternal(2.0f,
@@ -484,6 +567,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null));
assertTrue(
mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null));
+ verify(mWindowMagnifierCallback, times(4)).onMove(eq(displayId));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
index d3f30c508b8b..ccf2f8b16f8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
@@ -148,13 +148,13 @@ public class WindowMagnificationTest extends SysuiTestCase {
}
@Test
- public void onDrag_enabled_notifyCallback() throws RemoteException {
+ public void onMove_enabled_notifyCallback() throws RemoteException {
mCommandQueue.requestWindowMagnificationConnection(true);
waitForIdleSync();
- mWindowMagnification.onDrag(TEST_DISPLAY);
+ mWindowMagnification.onMove(TEST_DISPLAY);
- verify(mConnectionCallback).onDrag(TEST_DISPLAY);
+ verify(mConnectionCallback).onMove(TEST_DISPLAY);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java
deleted file mode 100644
index 619d48d1e306..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (C) 2021 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 static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.hardware.biometrics.ComponentInfoInternal;
-import android.hardware.biometrics.SensorLocationInternal;
-import android.hardware.biometrics.SensorProperties;
-import android.hardware.fingerprint.FingerprintSensorProperties;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.os.Bundle;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.View;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-@SmallTest
-public class AuthBiometricFaceToFingerprintViewTest extends SysuiTestCase {
-
- @Mock AuthBiometricView.Callback mCallback;
-
- private AuthBiometricFaceToFingerprintView mFaceToFpView;
-
- @Mock private Button mNegativeButton;
- @Mock private Button mCancelButton;
- @Mock private Button mConfirmButton;
- @Mock private Button mUseCredentialButton;
- @Mock private Button mTryAgainButton;
-
- @Mock private TextView mTitleView;
- @Mock private TextView mSubtitleView;
- @Mock private TextView mDescriptionView;
- @Mock private TextView mIndicatorView;
- @Mock private ImageView mIconView;
- @Mock private View mIconHolderView;
- @Mock private AuthBiometricFaceView.IconController mFaceIconController;
- @Mock private AuthBiometricFaceToFingerprintView.UdfpsIconController mUdfpsIconController;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
-
- mFaceToFpView = new TestableView(mContext);
- mFaceToFpView.mFaceIconController = mFaceIconController;
- mFaceToFpView.mUdfpsIconController = mUdfpsIconController;
- mFaceToFpView.setCallback(mCallback);
-
- mFaceToFpView.mNegativeButton = mNegativeButton;
- mFaceToFpView.mCancelButton = mCancelButton;
- mFaceToFpView.mUseCredentialButton = mUseCredentialButton;
- mFaceToFpView.mConfirmButton = mConfirmButton;
- mFaceToFpView.mTryAgainButton = mTryAgainButton;
- mFaceToFpView.mIndicatorView = mIndicatorView;
- }
-
- @Test
- public void testStateUpdated_whenDialogAnimatedIn() {
- mFaceToFpView.onDialogAnimatedIn();
- verify(mFaceToFpView.mFaceIconController)
- .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
- verify(mFaceToFpView.mUdfpsIconController, never()).updateState(anyInt());
- }
-
- @Test
- public void testIconUpdatesState_whenDialogStateUpdated() {
- mFaceToFpView.onDialogAnimatedIn();
- verify(mFaceToFpView.mFaceIconController)
- .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
- verify(mFaceToFpView.mUdfpsIconController, never()).updateState(anyInt());
-
- mFaceToFpView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATED);
- verify(mFaceToFpView.mFaceIconController).updateState(
- eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING),
- eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATED));
- verify(mFaceToFpView.mUdfpsIconController, never()).updateState(anyInt());
-
- assertEquals(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATED, mFaceToFpView.mState);
- }
-
- @Test
- public void testStateUpdated_whenSwitchToFingerprint() {
- mFaceToFpView.onDialogAnimatedIn();
- verify(mFaceToFpView.mFaceIconController)
- .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
-
- mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_ERROR);
-
- verify(mFaceToFpView.mFaceIconController).deactivate();
- verify(mFaceToFpView.mUdfpsIconController).updateState(
- eq(AuthBiometricFaceToFingerprintView.STATE_IDLE));
- verify(mConfirmButton).setVisibility(eq(View.GONE));
-
- mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING);
-
- verify(mFaceToFpView.mUdfpsIconController).updateState(
- eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
- }
-
- @Test
- public void testStateUpdated_whenSwitchToFingerprint_invokesCallbacks() {
- class TestModalityListener implements ModalityListener {
- public int switchCount = 0;
-
- @Override
- public void onModalitySwitched(int oldModality, int newModality) {
- assertEquals(TYPE_FINGERPRINT, newModality);
- assertEquals(TYPE_FACE, oldModality);
- switchCount++;
- }
- }
- final TestModalityListener modalityListener = new TestModalityListener();
-
- mFaceToFpView.onDialogAnimatedIn();
- mFaceToFpView.setModalityListener(modalityListener);
-
- assertEquals(0, modalityListener.switchCount);
-
- mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_ERROR);
- mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING);
-
- assertEquals(1, modalityListener.switchCount);
- }
-
- @Test
- @Ignore("flaky, b/189031816")
- public void testModeUpdated_onSoftError_whenSwitchToFingerprint() {
- mFaceToFpView.onDialogAnimatedIn();
- mFaceToFpView.onAuthenticationFailed(TYPE_FACE, "no face");
- waitForIdleSync();
-
- verify(mIndicatorView).setText(
- eq(mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead)));
- verify(mCallback).onAction(
- eq(AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR));
-
- // First we enter the error state, since we need to show the error animation/text. The
- // error state is later cleared based on a timer, and we enter STATE_AUTHENTICATING.
- assertEquals(AuthBiometricFaceToFingerprintView.STATE_ERROR, mFaceToFpView.mState);
- }
-
- @Test
- @Ignore("flaky, b/189031816")
- public void testModeUpdated_onHardError_whenSwitchToFingerprint() {
- mFaceToFpView.onDialogAnimatedIn();
- mFaceToFpView.onError(TYPE_FACE, "oh no!");
- waitForIdleSync();
-
- verify(mIndicatorView).setText(
- eq(mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead)));
- verify(mCallback).onAction(
- eq(AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR));
-
- // First we enter the error state, since we need to show the error animation/text. The
- // error state is later cleared based on a timer, and we enter STATE_AUTHENTICATING.
- assertEquals(AuthBiometricFaceToFingerprintView.STATE_ERROR, mFaceToFpView.mState);
- }
-
- @Test
- public void testFingerprintOnlyStartsOnFirstError() {
- mFaceToFpView.onDialogAnimatedIn();
- verify(mFaceToFpView.mFaceIconController)
- .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
-
- mFaceToFpView.onDialogAnimatedIn();
- mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_ERROR);
- mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING);
-
- reset(mCallback);
-
- mFaceToFpView.onError(TYPE_FACE, "oh no!");
- mFaceToFpView.onAuthenticationFailed(TYPE_FACE, "no face");
-
- verify(mCallback, never()).onAction(
- eq(AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR));
- }
-
- @Test
- public void testOnSaveState() {
- final FingerprintSensorPropertiesInternal sensorProps = createFingerprintSensorProps();
- mFaceToFpView.setFingerprintSensorProps(sensorProps);
-
- final Bundle savedState = new Bundle();
- mFaceToFpView.onSaveState(savedState);
-
- assertEquals(savedState.getInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE),
- mFaceToFpView.getActiveSensorType());
- assertEquals(savedState.getParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS), sensorProps);
- }
-
- @Test
- public void testRestoreState() {
- final Bundle savedState = new Bundle();
- savedState.putInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE, TYPE_FINGERPRINT);
- savedState.putParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS,
- createFingerprintSensorProps());
-
- mFaceToFpView.restoreState(savedState);
-
- assertEquals(mFaceToFpView.getActiveSensorType(), TYPE_FINGERPRINT);
- assertTrue(mFaceToFpView.isFingerprintUdfps());
- }
-
- @NonNull
- private static FingerprintSensorPropertiesInternal createFingerprintSensorProps() {
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- componentInfo.add(new ComponentInfoInternal("componentId", "hardwareVersion",
- "firmwareVersion", "serialNumber", "softwareVersion"));
-
- return new FingerprintSensorPropertiesInternal(
- 0 /* sensorId */,
- SensorProperties.STRENGTH_STRONG,
- 5 /* maxEnrollmentsPerUser */,
- componentInfo,
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
- true /* resetLockoutRequiresHardwareAuthToken */,
- List.of(new SensorLocationInternal("" /* displayId */,
- 540 /* sensorLocationX */,
- 1600 /* sensorLocationY */,
- 100 /* sensorRadius */)));
- }
-
- public class TestableView extends AuthBiometricFaceToFingerprintView {
- public TestableView(Context context) {
- super(context, null, new MockInjector());
- }
-
- @Override
- protected int getDelayAfterAuthenticatedDurationMs() {
- return 0;
- }
- }
-
- private class MockInjector extends AuthBiometricView.Injector {
- @Override
- public Button getNegativeButton() {
- return mNegativeButton;
- }
-
- @Override
- public Button getCancelButton() {
- return mCancelButton;
- }
-
- @Override
- public Button getUseCredentialButton() {
- return mUseCredentialButton;
- }
-
- @Override
- public Button getConfirmButton() {
- return mConfirmButton;
- }
-
- @Override
- public Button getTryAgainButton() {
- return mTryAgainButton;
- }
-
- @Override
- public TextView getTitleView() {
- return mTitleView;
- }
-
- @Override
- public TextView getSubtitleView() {
- return mSubtitleView;
- }
-
- @Override
- public TextView getDescriptionView() {
- return mDescriptionView;
- }
-
- @Override
- public TextView getIndicatorView() {
- return mIndicatorView;
- }
-
- @Override
- public ImageView getIconView() {
- return mIconView;
- }
-
- @Override
- public View getIconHolderView() {
- return mIconHolderView;
- }
-
- @Override
- public int getDelayAfterError() {
- return 0;
- }
-
- @Override
- public int getMediumToLargeAnimationDurationMs() {
- return 0;
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java
deleted file mode 100644
index b93381d2b5c9..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import com.android.systemui.R;
-
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class AuthBiometricFaceViewTest extends SysuiTestCase {
-
- @Mock
- AuthBiometricView.Callback mCallback;
-
- private TestableFaceView mFaceView;
-
- @Mock private Button mNegativeButton;
- @Mock private Button mCancelButton;
- @Mock private Button mUseCredentialButton;
-
- @Mock private Button mConfirmButton;
- @Mock private Button mTryAgainButton;
-
- @Mock private TextView mErrorView;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mFaceView = new TestableFaceView(mContext);
- mFaceView.mFaceIconController = mock(TestableFaceView.TestableIconController.class);
- mFaceView.setCallback(mCallback);
-
- mFaceView.mNegativeButton = mNegativeButton;
- mFaceView.mCancelButton = mCancelButton;
- mFaceView.mUseCredentialButton = mUseCredentialButton;
-
- mFaceView.mConfirmButton = mConfirmButton;
- mFaceView.mTryAgainButton = mTryAgainButton;
-
- mFaceView.mIndicatorView = mErrorView;
- }
-
- @Test
- public void testStateUpdated_whenDialogAnimatedIn() {
- mFaceView.onDialogAnimatedIn();
- verify(mFaceView.mFaceIconController)
- .updateState(anyInt(), eq(AuthBiometricFaceView.STATE_AUTHENTICATING));
- }
-
- @Test
- public void testIconUpdatesState_whenDialogStateUpdated() {
- mFaceView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATING);
- verify(mFaceView.mFaceIconController)
- .updateState(anyInt(), eq(AuthBiometricFaceView.STATE_AUTHENTICATING));
-
- mFaceView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATED);
- verify(mFaceView.mFaceIconController).updateState(
- eq(AuthBiometricFaceView.STATE_AUTHENTICATING),
- eq(AuthBiometricFaceView.STATE_AUTHENTICATED));
- }
-
- public class TestableFaceView extends AuthBiometricFaceView {
-
- public class TestableIconController extends IconController {
- TestableIconController(Context context, ImageView iconView) {
- super(context, iconView, mock(TextView.class));
- }
-
- public void startPulsing() {
- // Stub for testing
- }
- }
-
- @Override
- protected int getDelayAfterAuthenticatedDurationMs() {
- return 0; // Keep this at 0 for tests to invoke callback immediately.
- }
-
- public TestableFaceView(Context context) {
- super(context);
- }
- }
-
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
index f8e38e4994bc..9418b50ff390 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
@@ -20,137 +20,109 @@ 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.systemui.biometrics.AuthBiometricView.Callback.ACTION_AUTHENTICATED;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import android.content.Context;
-import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.PromptInfo;
import android.os.Bundle;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
-import android.util.AttributeSet;
+import android.testing.ViewUtils;
+import android.view.LayoutInflater;
import android.view.View;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+@Ignore
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
public class AuthBiometricViewTest extends SysuiTestCase {
- @Mock private AuthBiometricView.Callback mCallback;
- @Mock private AuthPanelController mPanelController;
-
- @Mock private Button mNegativeButton;
- @Mock private Button mCancelButton;
- @Mock private Button mUseCredentialButton;
-
- @Mock private Button mPositiveButton;
- @Mock private Button mTryAgainButton;
-
- @Mock private TextView mTitleView;
- @Mock private TextView mSubtitleView;
- @Mock private TextView mDescriptionView;
- @Mock private TextView mIndicatorView;
- @Mock private ImageView mIconView;
- @Mock private View mIconHolderView;
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
- private TestableBiometricView mBiometricView;
+ @Mock
+ private AuthBiometricView.Callback mCallback;
+ @Mock
+ private AuthPanelController mPanelController;
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- }
+ private AuthBiometricView mBiometricView;
@Test
public void testOnAuthenticationSucceeded_noConfirmationRequired_sendsActionAuthenticated() {
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
+ initDialog(false /* allowDeviceCredential */, mCallback);
// The onAuthenticated runnable is posted when authentication succeeds.
- mBiometricView.onAuthenticationSucceeded();
+ mBiometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT);
waitForIdleSync();
assertEquals(AuthBiometricView.STATE_AUTHENTICATED, mBiometricView.mState);
- verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED);
+ verify(mCallback).onAction(ACTION_AUTHENTICATED);
}
@Test
public void testOnAuthenticationSucceeded_confirmationRequired_updatesDialogContents() {
- final Button negativeButton = new Button(mContext);
- final Button cancelButton = new Button(mContext);
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() {
- @Override
- public Button getNegativeButton() {
- return negativeButton;
- }
-
- @Override
- public Button getCancelButton() {
- return cancelButton;
- }
- });
+ initDialog(false /* allowDeviceCredential */, mCallback);
mBiometricView.setRequireConfirmation(true);
- mBiometricView.onAuthenticationSucceeded();
+ mBiometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT);
waitForIdleSync();
- assertEquals(AuthBiometricView.STATE_PENDING_CONFIRMATION, mBiometricView.mState);
- verify(mCallback, never()).onAction(anyInt());
- assertEquals(View.GONE, negativeButton.getVisibility());
- assertEquals(View.VISIBLE, cancelButton.getVisibility());
- assertTrue(cancelButton.isEnabled());
+ // TODO: this should be tested in the subclasses
+ if (mBiometricView.supportsRequireConfirmation()) {
+ assertEquals(AuthBiometricView.STATE_PENDING_CONFIRMATION, mBiometricView.mState);
+
+ verify(mCallback, never()).onAction(anyInt());
+
+ assertEquals(View.GONE, mBiometricView.mNegativeButton.getVisibility());
+ assertEquals(View.VISIBLE, mBiometricView.mCancelButton.getVisibility());
+ assertTrue(mBiometricView.mCancelButton.isEnabled());
+
+ assertTrue(mBiometricView.mConfirmButton.isEnabled());
+ assertEquals(mContext.getText(R.string.biometric_dialog_tap_confirm),
+ mBiometricView.mIndicatorView.getText());
+ assertEquals(View.VISIBLE, mBiometricView.mIndicatorView.getVisibility());
+ } else {
+ assertEquals(AuthBiometricView.STATE_AUTHENTICATED, mBiometricView.mState);
+ verify(mCallback).onAction(eq(ACTION_AUTHENTICATED));
+ }
- verify(mBiometricView.mConfirmButton).setEnabled(eq(true));
- verify(mIndicatorView).setText(eq(R.string.biometric_dialog_tap_confirm));
- verify(mIndicatorView).setVisibility(eq(View.VISIBLE));
}
@Test
public void testPositiveButton_sendsActionAuthenticated() {
- Button button = new Button(mContext);
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() {
- @Override
- public Button getConfirmButton() {
- return button;
- }
- });
-
- button.performClick();
+ initDialog(false /* allowDeviceCredential */, mCallback);
+
+ mBiometricView.mConfirmButton.performClick();
waitForIdleSync();
- verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED);
+ verify(mCallback).onAction(ACTION_AUTHENTICATED);
assertEquals(AuthBiometricView.STATE_AUTHENTICATED, mBiometricView.mState);
}
@Test
public void testNegativeButton_beforeAuthentication_sendsActionButtonNegative() {
- Button button = new Button(mContext);
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() {
- @Override
- public Button getNegativeButton() {
- return button;
- }
- });
+ initDialog(false /* allowDeviceCredential */, mCallback);
mBiometricView.onDialogAnimatedIn();
- button.performClick();
+ mBiometricView.mNegativeButton.performClick();
waitForIdleSync();
verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE);
@@ -158,25 +130,14 @@ public class AuthBiometricViewTest extends SysuiTestCase {
@Test
public void testCancelButton_whenPendingConfirmation_sendsActionUserCanceled() {
- Button cancelButton = new Button(mContext);
- Button negativeButton = new Button(mContext);
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() {
- @Override
- public Button getNegativeButton() {
- return negativeButton;
- }
- @Override
- public Button getCancelButton() {
- return cancelButton;
- }
- });
+ initDialog(false /* allowDeviceCredential */, mCallback);
mBiometricView.setRequireConfirmation(true);
- mBiometricView.onAuthenticationSucceeded();
+ mBiometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT);
- assertEquals(View.GONE, negativeButton.getVisibility());
+ assertEquals(View.GONE, mBiometricView.mNegativeButton.getVisibility());
- cancelButton.performClick();
+ mBiometricView.mCancelButton.performClick();
waitForIdleSync();
verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_USER_CANCELED);
@@ -184,15 +145,9 @@ public class AuthBiometricViewTest extends SysuiTestCase {
@Test
public void testTryAgainButton_sendsActionTryAgain() {
- Button button = new Button(mContext);
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() {
- @Override
- public Button getTryAgainButton() {
- return button;
- }
- });
-
- button.performClick();
+ initDialog(false /* allowDeviceCredential */, mCallback);
+
+ mBiometricView.mTryAgainButton.performClick();
waitForIdleSync();
verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
@@ -202,7 +157,7 @@ public class AuthBiometricViewTest extends SysuiTestCase {
@Test
@Ignore("flaky, b/189031816")
public void testError_sendsActionError() {
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
+ initDialog(false /* allowDeviceCredential */, mCallback);
final String testError = "testError";
mBiometricView.onError(TYPE_FACE, testError);
waitForIdleSync();
@@ -213,7 +168,7 @@ public class AuthBiometricViewTest extends SysuiTestCase {
@Test
public void testBackgroundClicked_sendsActionUserCanceled() {
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
+ initDialog(false /* allowDeviceCredential */, mCallback);
View view = new View(mContext);
mBiometricView.setBackgroundView(view);
@@ -223,18 +178,18 @@ public class AuthBiometricViewTest extends SysuiTestCase {
@Test
public void testBackgroundClicked_afterAuthenticated_neverSendsUserCanceled() {
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
+ initDialog(false /* allowDeviceCredential */, mCallback);
View view = new View(mContext);
mBiometricView.setBackgroundView(view);
- mBiometricView.onAuthenticationSucceeded();
+ mBiometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT);
view.performClick();
verify(mCallback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED));
}
@Test
public void testBackgroundClicked_whenSmallDialog_neverSendsUserCanceled() {
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
+ initDialog(false /* allowDeviceCredential */, mCallback);
mBiometricView.mLayoutParams = new AuthDialog.LayoutParams(0, 0);
mBiometricView.updateSize(AuthDialog.SIZE_SMALL);
@@ -246,7 +201,7 @@ public class AuthBiometricViewTest extends SysuiTestCase {
@Test
public void testIgnoresUselessHelp() {
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
+ initDialog(false /* allowDeviceCredential */, mCallback);
mBiometricView.onDialogAnimatedIn();
waitForIdleSync();
@@ -256,33 +211,16 @@ public class AuthBiometricViewTest extends SysuiTestCase {
mBiometricView.onHelp(TYPE_FINGERPRINT, "");
waitForIdleSync();
- verify(mIndicatorView, never()).setText(any());
+ assertEquals("", mBiometricView.mIndicatorView.getText());
verify(mCallback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_ERROR));
assertEquals(AuthBiometricView.STATE_AUTHENTICATING, mBiometricView.mState);
}
@Test
public void testRestoresState() {
- final boolean requireConfirmation = true; // set/init from AuthController
-
- Button tryAgainButton = new Button(mContext);
- TextView indicatorView = new TextView(mContext);
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() {
- @Override
- public Button getTryAgainButton() {
- return tryAgainButton;
- }
- @Override
- public TextView getIndicatorView() {
- return indicatorView;
- }
-
- @Override
- public int getDelayAfterError() {
- // keep a real delay to test saving in the error state
- return BiometricPrompt.HIDE_DIALOG_DELAY;
- }
- });
+ final boolean requireConfirmation = true;
+
+ initDialog(false /* allowDeviceCredential */, mCallback, null, 10000);
final String failureMessage = "testFailureMessage";
mBiometricView.setRequireConfirmation(requireConfirmation);
@@ -292,8 +230,8 @@ public class AuthBiometricViewTest extends SysuiTestCase {
Bundle state = new Bundle();
mBiometricView.onSaveState(state);
- assertEquals(View.VISIBLE, tryAgainButton.getVisibility());
- assertEquals(View.VISIBLE, state.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY));
+ assertEquals(View.GONE, mBiometricView.mTryAgainButton.getVisibility());
+ assertEquals(View.GONE, state.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY));
assertEquals(AuthBiometricView.STATE_ERROR, mBiometricView.mState);
assertEquals(AuthBiometricView.STATE_ERROR, state.getInt(AuthDialog.KEY_BIOMETRIC_STATE));
@@ -307,25 +245,12 @@ public class AuthBiometricViewTest extends SysuiTestCase {
// TODO: Test dialog size. Should move requireConfirmation to buildBiometricPromptBundle
// Create new dialog and restore the previous state into it
- Button tryAgainButton2 = new Button(mContext);
- TextView indicatorView2 = new TextView(mContext);
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, state,
- new MockInjector() {
- @Override
- public Button getTryAgainButton() {
- return tryAgainButton2;
- }
-
- @Override
- public TextView getIndicatorView() {
- return indicatorView2;
- }
- });
+ initDialog(false /* allowDeviceCredential */, mCallback, state, 10000);
+ mBiometricView.mAnimationDurationHideDialog = 10000;
mBiometricView.setRequireConfirmation(requireConfirmation);
waitForIdleSync();
- // Test restored state
- assertEquals(View.VISIBLE, tryAgainButton.getVisibility());
+ assertEquals(View.GONE, mBiometricView.mTryAgainButton.getVisibility());
assertEquals(AuthBiometricView.STATE_ERROR, mBiometricView.mState);
assertEquals(View.VISIBLE, mBiometricView.mIndicatorView.getVisibility());
@@ -334,23 +259,12 @@ public class AuthBiometricViewTest extends SysuiTestCase {
}
@Test
- public void testCredentialButton_whenDeviceCredentialAllowed() {
- final Button negativeButton = new Button(mContext);
- final Button useCredentialButton = new Button(mContext);
- initDialog(mContext, true /* allowDeviceCredential */, mCallback, new MockInjector() {
- @Override
- public Button getNegativeButton() {
- return negativeButton;
- }
-
- @Override
- public Button getUseCredentialButton() {
- return useCredentialButton;
- }
- });
-
- assertEquals(View.GONE, negativeButton.getVisibility());
- useCredentialButton.performClick();
+ public void testCredentialButton_whenDeviceCredentialAllowed() throws InterruptedException {
+ initDialog(true /* allowDeviceCredential */, mCallback);
+
+ assertEquals(View.VISIBLE, mBiometricView.mUseCredentialButton.getVisibility());
+ assertEquals(View.GONE, mBiometricView.mNegativeButton.getVisibility());
+ mBiometricView.mUseCredentialButton.performClick();
waitForIdleSync();
verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL);
@@ -369,120 +283,30 @@ public class AuthBiometricViewTest extends SysuiTestCase {
return promptInfo;
}
- private void initDialog(Context context, boolean allowDeviceCredential,
- AuthBiometricView.Callback callback,
- Bundle savedState, MockInjector injector) {
- mBiometricView = new TestableBiometricView(context, null, injector);
+ private void initDialog(boolean allowDeviceCredential, AuthBiometricView.Callback callback) {
+ initDialog(allowDeviceCredential, callback,
+ null /* savedState */, 0 /* hideDelay */);
+ }
+
+ private void initDialog(boolean allowDeviceCredential,
+ AuthBiometricView.Callback callback, Bundle savedState, int hideDelay) {
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+ mBiometricView = (AuthBiometricView) inflater.inflate(
+ R.layout.auth_biometric_view, null, false);
+ mBiometricView.mAnimationDurationLong = 0;
+ mBiometricView.mAnimationDurationShort = 0;
+ mBiometricView.mAnimationDurationHideDialog = hideDelay;
mBiometricView.setPromptInfo(buildPromptInfo(allowDeviceCredential));
mBiometricView.setCallback(callback);
mBiometricView.restoreState(savedState);
- mBiometricView.onFinishInflateInternal();
- mBiometricView.onAttachedToWindowInternal();
-
+ ViewUtils.attachView(mBiometricView);
mBiometricView.setPanelController(mPanelController);
+ waitForIdleSync();
}
- private void initDialog(Context context, boolean allowDeviceCredential,
- AuthBiometricView.Callback callback, MockInjector injector) {
- initDialog(context, allowDeviceCredential, callback, null /* savedState */, injector);
- }
-
- private class MockInjector extends AuthBiometricView.Injector {
- @Override
- public Button getNegativeButton() {
- return mNegativeButton;
- }
-
- @Override
- public Button getCancelButton() {
- return mCancelButton;
- }
-
- @Override
- public Button getUseCredentialButton() {
- return mUseCredentialButton;
- }
-
- @Override
- public Button getConfirmButton() {
- return mPositiveButton;
- }
-
- @Override
- public Button getTryAgainButton() {
- return mTryAgainButton;
- }
-
- @Override
- public TextView getTitleView() {
- return mTitleView;
- }
-
- @Override
- public TextView getSubtitleView() {
- return mSubtitleView;
- }
-
- @Override
- public TextView getDescriptionView() {
- return mDescriptionView;
- }
-
- @Override
- public TextView getIndicatorView() {
- return mIndicatorView;
- }
-
- @Override
- public ImageView getIconView() {
- return mIconView;
- }
-
- @Override
- public View getIconHolderView() {
- return mIconHolderView;
- }
-
- @Override
- public int getDelayAfterError() {
- return 0; // Keep this at 0 for tests to invoke callback immediately.
- }
-
- @Override
- public int getMediumToLargeAnimationDurationMs() {
- return 0;
- }
- }
-
- private class TestableBiometricView extends AuthBiometricView {
- TestableBiometricView(Context context, AttributeSet attrs,
- Injector injector) {
- super(context, attrs, injector);
- }
-
- @Override
- protected int getDelayAfterAuthenticatedDurationMs() {
- return 0; // Keep this at 0 for tests to invoke callback immediately.
- }
-
- @Override
- protected int getStateForAfterError() {
- return 0;
- }
-
- @Override
- protected void handleResetAfterError() {
-
- }
-
- @Override
- protected void handleResetAfterHelp() {
-
- }
-
- @Override
- protected boolean supportsSmallDialog() {
- return false;
- }
+ @Override
+ protected void waitForIdleSync() {
+ TestableLooper.get(this).processAllMessages();
+ super.waitForIdleSync();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
deleted file mode 100644
index ae1268d48af9..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import static android.hardware.biometrics.BiometricManager.Authenticators;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
-
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.ComponentInfoInternal;
-import android.hardware.biometrics.PromptInfo;
-import android.hardware.biometrics.SensorProperties;
-import android.hardware.face.FaceSensorPropertiesInternal;
-import android.hardware.fingerprint.FingerprintSensorProperties;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.os.IBinder;
-import android.os.UserManager;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ScrollView;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class AuthContainerViewTest extends SysuiTestCase {
-
- private TestableAuthContainer mAuthContainer;
-
- private @Mock AuthDialogCallback mCallback;
- private @Mock UserManager mUserManager;
- private @Mock WakefulnessLifecycle mWakefulnessLifecycle;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- }
-
- @Test
- public void testActionAuthenticated_sendsDismissedAuthenticated() {
- initializeContainer(Authenticators.BIOMETRIC_WEAK);
-
- mAuthContainer.mBiometricCallback.onAction(
- AuthBiometricView.Callback.ACTION_AUTHENTICATED);
- verify(mCallback).onDismissed(
- eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED),
- eq(null) /* credentialAttestation */);
- }
-
- @Test
- public void testActionUserCanceled_sendsDismissedUserCanceled() {
- initializeContainer(Authenticators.BIOMETRIC_WEAK);
-
- mAuthContainer.mBiometricCallback.onAction(
- AuthBiometricView.Callback.ACTION_USER_CANCELED);
- verify(mCallback).onSystemEvent(eq(
- BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL));
- verify(mCallback).onDismissed(
- eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
- eq(null) /* credentialAttestation */);
- }
-
- @Test
- public void testActionButtonNegative_sendsDismissedButtonNegative() {
- initializeContainer(Authenticators.BIOMETRIC_WEAK);
-
- mAuthContainer.mBiometricCallback.onAction(
- AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE);
- verify(mCallback).onDismissed(
- eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE),
- eq(null) /* credentialAttestation */);
- }
-
- @Test
- public void testActionTryAgain_sendsTryAgain() {
- initializeContainer(Authenticators.BIOMETRIC_WEAK);
-
- mAuthContainer.mBiometricCallback.onAction(
- AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
- verify(mCallback).onTryAgainPressed();
- }
-
- @Test
- public void testActionError_sendsDismissedError() {
- initializeContainer(Authenticators.BIOMETRIC_WEAK);
-
- mAuthContainer.mBiometricCallback.onAction(
- AuthBiometricView.Callback.ACTION_ERROR);
- verify(mCallback).onDismissed(
- eq(AuthDialogCallback.DISMISSED_ERROR),
- eq(null) /* credentialAttestation */);
- }
-
- @Test
- public void testActionUseDeviceCredential_sendsOnDeviceCredentialPressed() {
- initializeContainer(
- Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
-
- mAuthContainer.mBiometricCallback.onAction(
- AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL);
- verify(mCallback).onDeviceCredentialPressed();
-
- // Credential view is attached to the frame layout
- waitForIdleSync();
- assertNotNull(mAuthContainer.mCredentialView);
- verify(mAuthContainer.mFrameLayout).addView(eq(mAuthContainer.mCredentialView));
- }
-
- @Test
- public void testAnimateToCredentialUI_invokesStartTransitionToCredentialUI() {
- initializeContainer(
- Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
-
- mAuthContainer.mBiometricView = mock(AuthBiometricView.class);
- mAuthContainer.animateToCredentialUI();
- verify(mAuthContainer.mBiometricView).startTransitionToCredentialUI();
- }
-
- @Test
- public void testShowBiometricUI() {
- initializeContainer(Authenticators.BIOMETRIC_WEAK);
-
- assertNotEquals(null, mAuthContainer.mBiometricView);
-
- mAuthContainer.onAttachedToWindowInternal();
- verify(mAuthContainer.mBiometricScrollView).addView(mAuthContainer.mBiometricView);
- // Credential view is not added
- verify(mAuthContainer.mFrameLayout, never()).addView(any());
- }
-
- @Test
- public void testShowCredentialUI_doesNotInflateBiometricUI() {
- initializeContainer(Authenticators.DEVICE_CREDENTIAL);
-
- mAuthContainer.onAttachedToWindowInternal();
-
- assertNull(null, mAuthContainer.mBiometricView);
- assertNotNull(mAuthContainer.mCredentialView);
- verify(mAuthContainer.mFrameLayout).addView(mAuthContainer.mCredentialView);
- }
-
- @Test
- public void testCredentialViewUsesEffectiveUserId() {
- final int dummyEffectiveUserId = 200;
- when(mUserManager.getCredentialOwnerProfile(anyInt())).thenReturn(dummyEffectiveUserId);
-
- initializeContainer(Authenticators.DEVICE_CREDENTIAL);
- mAuthContainer.onAttachedToWindowInternal();
- assertTrue(mAuthContainer.mCredentialView instanceof AuthCredentialPatternView);
- assertEquals(dummyEffectiveUserId, mAuthContainer.mCredentialView.mEffectiveUserId);
- assertEquals(Utils.CREDENTIAL_PATTERN, mAuthContainer.mCredentialView.mCredentialType);
- }
-
- @Test
- public void testCredentialUI_disablesClickingOnBackground() {
- // In the credential view, clicking on the background (to cancel authentication) is not
- // valid. Thus, the listener should be null, and it should not be in the accessibility
- // hierarchy.
- initializeContainer(Authenticators.DEVICE_CREDENTIAL);
-
- mAuthContainer.onAttachedToWindowInternal();
-
- verify(mAuthContainer.mBackgroundView).setOnClickListener(eq(null));
- verify(mAuthContainer.mBackgroundView).setImportantForAccessibility(
- eq(View.IMPORTANT_FOR_ACCESSIBILITY_NO));
- }
-
- @Test
- public void testOnDialogAnimatedIn_sendsCancelReason_whenPendingDismiss() {
- initializeContainer(Authenticators.BIOMETRIC_WEAK);
- mAuthContainer.mContainerState = AuthContainerView.STATE_PENDING_DISMISS;
- mAuthContainer.onDialogAnimatedIn();
- verify(mCallback).onDismissed(
- eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
- eq(null) /* credentialAttestation */);
- }
-
- @Test
- public void testLayoutParams_hasSecureWindowFlag() {
- final IBinder windowToken = mock(IBinder.class);
- final WindowManager.LayoutParams layoutParams =
- AuthContainerView.getLayoutParams(windowToken, "");
- assertTrue((layoutParams.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0);
- }
-
- @Test
- public void testLayoutParams_excludesImeInsets() {
- final IBinder windowToken = mock(IBinder.class);
- final WindowManager.LayoutParams layoutParams =
- AuthContainerView.getLayoutParams(windowToken, "");
- assertTrue((layoutParams.getFitInsetsTypes() & WindowInsets.Type.ime()) == 0);
- }
-
- private void initializeContainer(int authenticators) {
- AuthContainerView.Config config = new AuthContainerView.Config();
- config.mContext = mContext;
- config.mCallback = mCallback;
- config.mSensorIds = new int[] {0};
- config.mCredentialAllowed = false;
-
- PromptInfo promptInfo = new PromptInfo();
- promptInfo.setAuthenticators(authenticators);
- config.mPromptInfo = promptInfo;
-
- final List<FingerprintSensorPropertiesInternal> fpProps = new ArrayList<>();
-
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
- "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
- "00000001" /* serialNumber */, "" /* softwareVersion */));
- componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
- "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
- "vendor/version/revision" /* softwareVersion */));
-
- fpProps.add(new FingerprintSensorPropertiesInternal(0,
- SensorProperties.STRENGTH_STRONG,
- 5 /* maxEnrollmentsPerUser */,
- componentInfo,
- FingerprintSensorProperties.TYPE_REAR,
- false /* resetLockoutRequiresHardwareAuthToken */));
- mAuthContainer = new TestableAuthContainer(config, fpProps, null /* faceProps */,
- mWakefulnessLifecycle);
- }
-
- private class TestableAuthContainer extends AuthContainerView {
- TestableAuthContainer(AuthContainerView.Config config,
- @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
- @Nullable List<FaceSensorPropertiesInternal> faceProps,
- WakefulnessLifecycle wakefulnessLifecycle) {
-
- super(config, new MockInjector(), fpProps, faceProps, wakefulnessLifecycle);
- }
-
- @Override
- public void animateAway(int reason) {
- // TODO: Credential attestation should be testable/tested
- mConfig.mCallback.onDismissed(reason, null /* credentialAttestation */);
- }
- }
-
- private final class MockInjector extends AuthContainerView.Injector {
- @Override
- public ScrollView getBiometricScrollView(FrameLayout parent) {
- return mock(ScrollView.class);
- }
-
- @Override
- public FrameLayout inflateContainerView(LayoutInflater factory, ViewGroup root) {
- return mock(FrameLayout.class);
- }
-
- @Override
- public AuthPanelController getPanelController(Context context, View view) {
- return mock(AuthPanelController.class);
- }
-
- @Override
- public ImageView getBackgroundView(FrameLayout parent) {
- return mock(ImageView.class);
- }
-
- @Override
- public View getPanelView(FrameLayout parent) {
- return mock(View.class);
- }
-
- @Override
- public int getAnimateCredentialStartDelayMs() {
- return 0;
- }
-
- @Override
- public UserManager getUserManager(Context context) {
- return mUserManager;
- }
-
- @Override
- public @Utils.CredentialType int getCredentialType(Context context, int effectiveUserId) {
- return Utils.CREDENTIAL_PATTERN;
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
new file mode 100644
index 000000000000..6f0a8a6adfef
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -0,0 +1,324 @@
+/*
+ * 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.biometrics
+
+import android.app.admin.DevicePolicyManager
+import android.hardware.biometrics.BiometricConstants
+import android.hardware.biometrics.BiometricManager
+import android.hardware.biometrics.ComponentInfoInternal
+import android.hardware.biometrics.PromptInfo
+import android.hardware.biometrics.SensorProperties
+import android.hardware.face.FaceSensorPropertiesInternal
+import android.hardware.fingerprint.FingerprintSensorProperties
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.os.Handler
+import android.os.IBinder
+import android.os.UserManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.ViewUtils
+import android.view.View
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.widget.ScrollView
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
+
+@Ignore
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+@SmallTest
+class AuthContainerViewTest : SysuiTestCase() {
+
+ @JvmField @Rule
+ var rule = MockitoJUnit.rule()
+
+ @Mock
+ lateinit var callback: AuthDialogCallback
+ @Mock
+ lateinit var userManager: UserManager
+ @Mock
+ lateinit var lockPatternUtils: LockPatternUtils
+ @Mock
+ lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock
+ lateinit var windowToken: IBinder
+
+ private lateinit var authContainer: TestAuthContainerView
+
+ @Test
+ fun testActionAuthenticated_sendsDismissedAuthenticated() {
+ initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+ authContainer.mBiometricCallback.onAction(
+ AuthBiometricView.Callback.ACTION_AUTHENTICATED
+ )
+ waitForIdleSync()
+
+ verify(callback).onDismissed(
+ eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED),
+ eq<ByteArray?>(null) /* credentialAttestation */
+ )
+ assertThat(authContainer.parent).isNull()
+ }
+
+ @Test
+ fun testActionUserCanceled_sendsDismissedUserCanceled() {
+ initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+ authContainer.mBiometricCallback.onAction(
+ AuthBiometricView.Callback.ACTION_USER_CANCELED
+ )
+ waitForIdleSync()
+
+ verify(callback).onSystemEvent(
+ eq(BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL)
+ )
+ verify(callback).onDismissed(
+ eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
+ eq<ByteArray?>(null) /* credentialAttestation */
+ )
+ assertThat(authContainer.parent).isNull()
+ }
+
+ @Test
+ fun testActionButtonNegative_sendsDismissedButtonNegative() {
+ initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+ authContainer.mBiometricCallback.onAction(
+ AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE
+ )
+ waitForIdleSync()
+
+ verify(callback).onDismissed(
+ eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE),
+ eq<ByteArray?>(null) /* credentialAttestation */
+ )
+ assertThat(authContainer.parent).isNull()
+ }
+
+ @Test
+ fun testActionTryAgain_sendsTryAgain() {
+ initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+ authContainer.mBiometricCallback.onAction(
+ AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN
+ )
+ waitForIdleSync()
+
+ verify(callback).onTryAgainPressed()
+ }
+
+ @Test
+ fun testActionError_sendsDismissedError() {
+ initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+ authContainer.mBiometricCallback.onAction(
+ AuthBiometricView.Callback.ACTION_ERROR
+ )
+ waitForIdleSync()
+
+ verify(callback).onDismissed(
+ eq(AuthDialogCallback.DISMISSED_ERROR),
+ eq<ByteArray?>(null) /* credentialAttestation */
+ )
+ assertThat(authContainer.parent).isNull()
+ }
+
+ @Test
+ fun testActionUseDeviceCredential_sendsOnDeviceCredentialPressed() {
+ initializeContainer(
+ BiometricManager.Authenticators.BIOMETRIC_WEAK or
+ BiometricManager.Authenticators.DEVICE_CREDENTIAL
+ )
+ authContainer.mBiometricCallback.onAction(
+ AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL
+ )
+ waitForIdleSync()
+
+ verify(callback).onDeviceCredentialPressed()
+ assertThat(authContainer.hasCredentialView()).isTrue()
+ }
+
+ @Test
+ fun testAnimateToCredentialUI_invokesStartTransitionToCredentialUI() {
+ initializeContainer(
+ BiometricManager.Authenticators.BIOMETRIC_WEAK or
+ BiometricManager.Authenticators.DEVICE_CREDENTIAL
+ )
+ authContainer.animateToCredentialUI()
+ waitForIdleSync()
+
+ assertThat(authContainer.hasCredentialView()).isTrue()
+ }
+
+ @Test
+ fun testShowBiometricUI() {
+ initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+
+ waitForIdleSync()
+
+ assertThat(authContainer.hasCredentialView()).isFalse()
+ assertThat(authContainer.hasBiometricPrompt()).isTrue()
+ }
+
+ @Test
+ fun testShowCredentialUI() {
+ initializeContainer(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
+ waitForIdleSync()
+
+ assertThat(authContainer.hasCredentialView()).isTrue()
+ assertThat(authContainer.hasBiometricPrompt()).isFalse()
+ }
+
+ @Test
+ fun testCredentialViewUsesEffectiveUserId() {
+ whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(200)
+ whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(200))).thenReturn(
+ DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+ )
+
+ initializeContainer(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
+ waitForIdleSync()
+
+ assertThat(authContainer.hasCredentialPatternView()).isTrue()
+ assertThat(authContainer.hasBiometricPrompt()).isFalse()
+ }
+
+ @Test
+ fun testCredentialUI_disablesClickingOnBackground() {
+ whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20)
+ whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn(
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+ )
+
+ // In the credential view, clicking on the background (to cancel authentication) is not
+ // valid. Thus, the listener should be null, and it should not be in the accessibility
+ // hierarchy.
+ initializeContainer(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
+ waitForIdleSync()
+
+ assertThat(authContainer.hasCredentialPasswordView()).isTrue()
+ assertThat(authContainer.hasBiometricPrompt()).isFalse()
+ assertThat(
+ authContainer.findViewById<View>(R.id.background)?.isImportantForAccessibility
+ ).isFalse()
+
+ authContainer.findViewById<View>(R.id.background)?.performClick()
+ waitForIdleSync()
+
+ assertThat(authContainer.hasCredentialPasswordView()).isTrue()
+ assertThat(authContainer.hasBiometricPrompt()).isFalse()
+ }
+
+ @Test
+ fun testLayoutParams_hasSecureWindowFlag() {
+ val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+ assertThat((layoutParams.flags and WindowManager.LayoutParams.FLAG_SECURE) != 0).isTrue()
+ }
+
+ @Test
+ fun testLayoutParams_excludesImeInsets() {
+ val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+ assertThat((layoutParams.fitInsetsTypes and WindowInsets.Type.ime()) == 0).isTrue()
+ }
+
+ private fun initializeContainer(authenticators: Int) {
+ val config = AuthContainerView.Config()
+ config.mContext = mContext
+ config.mCallback = callback
+ config.mSensorIds = intArrayOf(0)
+ config.mSkipAnimation = true
+ config.mPromptInfo = PromptInfo()
+ config.mPromptInfo.authenticators = authenticators
+ val componentInfo = listOf(
+ ComponentInfoInternal(
+ "faceSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */, "" /* softwareVersion */
+ ),
+ ComponentInfoInternal(
+ "matchingAlgorithm" /* componentId */,
+ "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
+ "vendor/version/revision" /* softwareVersion */
+ )
+ )
+ val fpProps = listOf(
+ FingerprintSensorPropertiesInternal(
+ 0,
+ SensorProperties.STRENGTH_STRONG,
+ 5 /* maxEnrollmentsPerUser */,
+ componentInfo,
+ FingerprintSensorProperties.TYPE_REAR,
+ false /* resetLockoutRequiresHardwareAuthToken */
+ )
+ )
+ authContainer = TestAuthContainerView(
+ config,
+ fpProps,
+ listOf(),
+ wakefulnessLifecycle,
+ userManager,
+ lockPatternUtils,
+ Handler(TestableLooper.get(this).looper)
+ )
+ ViewUtils.attachView(authContainer)
+ }
+
+ private inner class TestAuthContainerView(
+ config: Config,
+ fpProps: List<FingerprintSensorPropertiesInternal>,
+ faceProps: List<FaceSensorPropertiesInternal>,
+ wakefulnessLifecycle: WakefulnessLifecycle,
+ userManager: UserManager,
+ lockPatternUtils: LockPatternUtils,
+ mainHandler: Handler
+ ) : AuthContainerView(
+ config, fpProps, faceProps,
+ wakefulnessLifecycle, userManager, lockPatternUtils, mainHandler
+ ) {
+ override fun postOnAnimation(runnable: Runnable) {
+ runnable.run()
+ }
+ }
+
+ override fun waitForIdleSync() {
+ TestableLooper.get(this).processAllMessages()
+ super.waitForIdleSync()
+ }
+}
+
+private fun AuthContainerView.hasBiometricPrompt() =
+ (findViewById<ScrollView>(R.id.biometric_scrollview)?.childCount ?: 0) > 0
+
+private fun AuthContainerView.hasCredentialView() =
+ hasCredentialPatternView() || hasCredentialPasswordView()
+
+private fun AuthContainerView.hasCredentialPatternView() =
+ findViewById<View>(R.id.lockPattern) != null
+
+private fun AuthContainerView.hasCredentialPasswordView() =
+ findViewById<View>(R.id.lockPassword) != null
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 c37e966f3540..cfac96512582 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -16,8 +16,9 @@
package com.android.systemui.biometrics;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
-import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT;
+import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
@@ -62,6 +63,7 @@ import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
+import android.os.UserManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
@@ -71,6 +73,7 @@ import android.view.WindowManager;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
+import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -79,6 +82,8 @@ import com.android.systemui.util.concurrency.Execution;
import com.android.systemui.util.concurrency.FakeExecution;
import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.AdditionalMatchers;
@@ -86,7 +91,8 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
import java.util.List;
@@ -94,11 +100,15 @@ import java.util.Random;
import javax.inject.Provider;
+@Ignore
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
public class AuthControllerTest extends SysuiTestCase {
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
@Mock
private PackageManager mPackageManager;
@Mock
@@ -128,6 +138,10 @@ public class AuthControllerTest extends SysuiTestCase {
@Mock
private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock
+ private UserManager mUserManager;
+ @Mock
+ private LockPatternUtils mLockPatternUtils;
+ @Mock
private StatusBarStateController mStatusBarStateController;
@Captor
ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mAuthenticatorsRegisteredCaptor;
@@ -144,8 +158,6 @@ public class AuthControllerTest extends SysuiTestCase {
@Before
public void setup() throws RemoteException {
- MockitoAnnotations.initMocks(this);
-
mContextSpy = spy(mContext);
mExecution = new FakeExecution();
mTestableLooper = TestableLooper.get(this);
@@ -343,8 +355,8 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testOnAuthenticationSucceededInvoked_whenSystemRequested() {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
- mAuthController.onBiometricAuthenticated();
- verify(mDialog1).onAuthenticationSucceeded();
+ mAuthController.onBiometricAuthenticated(TYPE_FINGERPRINT);
+ verify(mDialog1).onAuthenticationSucceeded(eq(TYPE_FINGERPRINT));
}
@Test
@@ -528,8 +540,7 @@ public class AuthControllerTest extends SysuiTestCase {
doAnswer(invocation -> {
Object[] args = invocation.getArguments();
Bundle savedState = (Bundle) args[0];
- savedState.putInt(
- AuthDialog.KEY_CONTAINER_STATE, AuthContainerView.STATE_SHOWING);
+ savedState.putBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY, false);
return null; // onSaveState returns void
}).when(mDialog1).onSaveState(any());
@@ -558,8 +569,7 @@ public class AuthControllerTest extends SysuiTestCase {
doAnswer(invocation -> {
Object[] args = invocation.getArguments();
Bundle savedState = (Bundle) args[0];
- savedState.putInt(
- AuthDialog.KEY_CONTAINER_STATE, AuthContainerView.STATE_SHOWING);
+ savedState.putBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY, false);
savedState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, true);
return null; // onSaveState returns void
}).when(mDialog1).onSaveState(any());
@@ -697,7 +707,7 @@ public class AuthControllerTest extends SysuiTestCase {
0 /* operationId */,
"testPackage",
1 /* requestId */,
- BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT);
+ BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE);
}
private PromptInfo createTestPromptInfo() {
@@ -739,15 +749,16 @@ public class AuthControllerTest extends SysuiTestCase {
super(context, execution, commandQueue, activityTaskManager, windowManager,
fingerprintManager, faceManager, udfpsControllerFactory,
sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle,
- statusBarStateController, mHandler);
+ mUserManager, mLockPatternUtils, statusBarStateController, mHandler);
}
@Override
protected AuthDialog buildDialog(PromptInfo promptInfo,
- boolean requireConfirmation, int userId, int[] sensorIds, boolean credentialAllowed,
+ boolean requireConfirmation, int userId, int[] sensorIds,
String opPackageName, boolean skipIntro, long operationId, long requestId,
@BiometricManager.BiometricMultiSensorMode int multiSensorConfig,
- WakefulnessLifecycle wakefulnessLifecycle) {
+ WakefulnessLifecycle wakefulnessLifecycle, UserManager userManager,
+ LockPatternUtils lockPatternUtils) {
mLastBiometricPromptInfo = promptInfo;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
index 254fc5945522..839c0ab1318f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
@@ -17,10 +17,10 @@
package com.android.systemui.biometrics
import android.animation.Animator
-import android.graphics.Insets
import android.app.ActivityManager
import android.app.ActivityTaskManager
import android.content.ComponentName
+import android.graphics.Insets
import android.graphics.Rect
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
@@ -65,8 +65,8 @@ import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
import org.mockito.Mockito.anyFloat
-import org.mockito.Mockito.anyLong
import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyLong
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 066a866118dd..ef82c3ec3322 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -57,9 +57,9 @@ import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.mock
-import org.mockito.Mockito.`when` as whenever
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
index 2cd470e49d0c..3d8d1282e184 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
@@ -38,11 +38,11 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.nullable
import org.mockito.Mockito.never
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.Mockito.nullable
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
private const val DISPLAY_ID = "" // default display id
private const val SENSOR_X = 50
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 2860b50c2295..b3d54590dc99 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -28,6 +28,7 @@ import android.service.dreams.DreamService;
import android.service.dreams.IDreamOverlay;
import android.service.dreams.IDreamOverlayCallback;
import android.testing.AndroidTestingRunner;
+import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
@@ -99,6 +100,9 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
@Mock
DreamPreviewComplication mPreviewComplication;
+ @Mock
+ ViewGroup mDreamOverlayContainerViewParent;
+
DreamOverlayService mService;
@Before
@@ -152,6 +156,23 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
}
@Test
+ public void testDreamOverlayContainerViewRemovedFromOldParentWhenInitialized()
+ throws Exception {
+ when(mDreamOverlayContainerView.getParent())
+ .thenReturn(mDreamOverlayContainerViewParent)
+ .thenReturn(null);
+
+ final IBinder proxy = mService.onBind(new Intent());
+ final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+ // Inform the overlay service of dream starting.
+ overlay.startDream(mWindowParams, mDreamOverlayCallback);
+ mMainExecutor.runAllReady();
+
+ verify(mDreamOverlayContainerViewParent).removeView(mDreamOverlayContainerView);
+ }
+
+ @Test
public void testShouldShowComplicationsFalseByDefault() {
mService.onBind(new Intent());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 6c29ecc7ae50..11f76a381ad4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -448,9 +448,10 @@ public class CommandQueueTest extends SysuiTestCase {
@Test
public void testOnBiometricAuthenticated() {
- mCommandQueue.onBiometricAuthenticated();
+ final int id = 12;
+ mCommandQueue.onBiometricAuthenticated(id);
waitForIdleSync();
- verify(mCallbacks).onBiometricAuthenticated();
+ verify(mCallbacks).onBiometricAuthenticated(eq(id));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 466d954e7be0..3c1a73eb672e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -45,7 +45,6 @@ import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -66,15 +65,17 @@ import android.os.BatteryManager;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.view.ViewGroup;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.app.IBatteryStats;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
@@ -106,7 +107,8 @@ import java.text.NumberFormat;
import java.util.Collections;
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
public class KeyguardIndicationControllerTest extends SysuiTestCase {
private static final String ORGANIZATION_NAME = "organization";
@@ -164,11 +166,15 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
@Captor
private ArgumentCaptor<KeyguardIndication> mKeyguardIndicationCaptor;
@Captor
+ private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
+ @Captor
private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallbackCaptor;
private KeyguardStateController.Callback mKeyguardStateControllerCallback;
+ private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
private StatusBarStateController.StateListener mStatusBarStateListener;
private BroadcastReceiver mBroadcastReceiver;
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+ private TestableLooper mTestableLooper;
private KeyguardIndicationTextView mTextView; // AOD text
@@ -181,6 +187,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mTestableLooper = TestableLooper.get(this);
mTextView = new KeyguardIndicationTextView(mContext);
mTextView.setAnimationsEnabled(false);
@@ -226,7 +233,10 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
Looper.prepare();
}
- mController = new KeyguardIndicationController(mContext, mWakeLockBuilder,
+ mController = new KeyguardIndicationController(
+ mContext,
+ mTestableLooper.getLooper(),
+ mWakeLockBuilder,
mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor,
mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats,
mUserManager, mExecutor, mExecutor, mFalsingManager, mLockPatternUtils,
@@ -245,6 +255,10 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
mKeyguardStateControllerCallbackCaptor.capture());
mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
+ verify(mKeyguardUpdateMonitor).registerCallback(
+ mKeyguardUpdateMonitorCallbackCaptor.capture());
+ mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue();
+
mExecutor.runAllReady();
reset(mRotateTextViewController);
}
@@ -267,7 +281,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_POOR);
});
mInstrumentation.waitForIdleSync();
-
+ mTestableLooper.processAllMessages();
verifyIndicationMessage(INDICATION_TYPE_ALIGNMENT,
mContext.getResources().getString(R.string.dock_alignment_slow_charging));
@@ -285,6 +299,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_TERRIBLE);
});
mInstrumentation.waitForIdleSync();
+ mTestableLooper.processAllMessages();
verifyIndicationMessage(INDICATION_TYPE_ALIGNMENT,
mContext.getResources().getString(R.string.dock_alignment_not_charging));
@@ -303,6 +318,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_POOR);
});
mInstrumentation.waitForIdleSync();
+ mTestableLooper.processAllMessages();
assertThat(mTextView.getText()).isEqualTo(
mContext.getResources().getString(R.string.dock_alignment_slow_charging));
@@ -321,6 +337,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_TERRIBLE);
});
mInstrumentation.waitForIdleSync();
+ mTestableLooper.processAllMessages();
assertThat(mTextView.getText()).isEqualTo(
mContext.getResources().getString(R.string.dock_alignment_not_charging));
@@ -331,9 +348,12 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
@Test
public void disclosure_unmanaged() {
createController();
+ mController.setVisible(true);
when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(false);
+ reset(mRotateTextViewController);
+
sendUpdateDisclosureBroadcast();
mExecutor.runAllReady();
@@ -347,6 +367,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null);
sendUpdateDisclosureBroadcast();
+ mController.setVisible(true);
mExecutor.runAllReady();
verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureGeneric);
@@ -355,6 +376,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
@Test
public void disclosure_orgOwnedDeviceWithManagedProfile_noOrganizationName() {
createController();
+ mController.setVisible(true);
when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(true);
when(mUserManager.getProfiles(anyInt())).thenReturn(Collections.singletonList(
@@ -369,6 +391,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
@Test
public void disclosure_deviceOwner_withOrganizationName() {
createController();
+ mController.setVisible(true);
when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME);
@@ -381,6 +404,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
@Test
public void disclosure_orgOwnedDeviceWithManagedProfile_withOrganizationName() {
createController();
+ mController.setVisible(true);
when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(true);
when(mUserManager.getProfiles(anyInt())).thenReturn(Collections.singletonList(
@@ -397,6 +421,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
createController();
+ mController.setVisible(true);
when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null);
@@ -424,7 +449,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
@Test
public void disclosure_deviceOwner_financedDeviceWithOrganizationName() {
createController();
-
+ mController.setVisible(true);
when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME);
@@ -432,6 +457,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
.thenReturn(DEVICE_OWNER_TYPE_FINANCED);
sendUpdateDisclosureBroadcast();
mExecutor.runAllReady();
+ mController.setVisible(true);
verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mFinancedDisclosureWithOrganization);
}
@@ -469,10 +495,10 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
@Test
public void transientIndication_visibleWhenDozing() {
createController();
-
mController.setVisible(true);
- mController.showTransientIndication(TEST_STRING_RES);
+
mStatusBarStateListener.onDozingChanged(true);
+ mController.showTransientIndication(TEST_STRING_RES);
assertThat(mTextView.getText()).isEqualTo(
mContext.getResources().getString(TEST_STRING_RES));
@@ -493,7 +519,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
reset(mRotateTextViewController);
mStatusBarStateListener.onDozingChanged(true);
- verifyHideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+ assertThat(mTextView.getText()).isNotEqualTo(message);
}
@Test
@@ -604,10 +630,9 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
}
@Test
- public void updateMonitor_listener() {
+ public void registersKeyguardStateCallback() {
createController();
verify(mKeyguardStateController).addCallback(any());
- verify(mKeyguardUpdateMonitor, times(2)).registerCallback(any());
}
@Test
@@ -695,13 +720,13 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
@Test
public void onRefreshBatteryInfo_dozing_dischargingWithOverheat_presentBatteryPercentage() {
createController();
+ mController.setVisible(true);
BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_DISCHARGING,
90 /* level */, 0 /* plugged */, BatteryManager.BATTERY_HEALTH_OVERHEAT,
0 /* maxChargingWattage */, true /* present */);
mController.getKeyguardCallback().onRefreshBatteryInfo(status);
mStatusBarStateListener.onDozingChanged(true);
- mController.setVisible(true);
String percentage = NumberFormat.getPercentInstance().format(90 / 100f);
assertThat(mTextView.getText()).isEqualTo(percentage);
@@ -710,9 +735,9 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
@Test
public void onRequireUnlockForNfc_showsRequireUnlockForNfcIndication() {
createController();
+ mController.setVisible(true);
String message = mContext.getString(R.string.require_unlock_for_nfc);
mController.getKeyguardCallback().onRequireUnlockForNfc();
- mController.setVisible(true);
verifyTransientMessage(message);
}
@@ -778,6 +803,9 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
@Test
public void testOnKeyguardShowingChanged_showing_updatesPersistentMessages() {
createController();
+ mController.setVisible(true);
+ mExecutor.runAllReady();
+ reset(mRotateTextViewController);
// GIVEN keyguard is showing
when(mKeyguardStateController.isShowing()).thenReturn(true);
@@ -799,6 +827,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
@Test
public void onTrustGrantedMessageDoesNotShowUntilTrustGranted() {
createController();
+ mController.setVisible(true);
+ reset(mRotateTextViewController);
// GIVEN a trust granted message but trust isn't granted
final String trustGrantedMsg = "testing trust granted message";
@@ -808,7 +838,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
// WHEN trust is granted
when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
- mController.setVisible(true);
+ mKeyguardUpdateMonitorCallback.onTrustChanged(KeyguardUpdateMonitor.getCurrentUser());
// THEN verify the trust granted message shows
verifyIndicationMessage(
@@ -819,6 +849,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
@Test
public void onTrustGrantedMessageDoesShowsOnTrustGranted() {
createController();
+ mController.setVisible(true);
// GIVEN trust is granted
when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 077b41a0aa90..5f2bbd341962 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -126,12 +126,6 @@ public class DozeParametersTest extends SysuiTestCase {
setAodEnabledForTest(true);
setShouldControlUnlockedScreenOffForTest(true);
setDisplayNeedsBlankingForTest(false);
-
- // Default to false here (with one test to make sure that when it returns true, we respect
- // that). We'll test the specific conditions for this to return true/false in the
- // UnlockedScreenOffAnimationController's tests.
- when(mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation())
- .thenReturn(false);
}
@Test
@@ -180,12 +174,9 @@ public class DozeParametersTest extends SysuiTestCase {
*/
@Test
public void testControlUnlockedScreenOffAnimation_dozeAfterScreenOff_false() {
- mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(true);
-
// If AOD is disabled, we shouldn't want to control screen off. Also, let's double check
// that when that value is updated, we called through to PowerManager.
setAodEnabledForTest(false);
-
assertFalse(mDozeParameters.shouldControlScreenOff());
assertTrue(mPowerManagerDozeAfterScreenOff);
@@ -197,6 +188,7 @@ public class DozeParametersTest extends SysuiTestCase {
@Test
public void testControlUnlockedScreenOffAnimationDisabled_dozeAfterScreenOff() {
+ setShouldControlUnlockedScreenOffForTest(true);
when(mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)).thenReturn(false);
assertFalse(mDozeParameters.shouldControlUnlockedScreenOff());
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 0936b773d4b3..050563a5707c 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
@@ -31,7 +31,6 @@ import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.StatusBarStateControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.settings.GlobalSettings
-import junit.framework.Assert.assertFalse
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -134,7 +133,7 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
*/
@Test
fun testAodUiShownIfNotInteractive() {
- `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(true)
+ `when`(dozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true)
`when`(powerManager.isInteractive).thenReturn(false)
val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java)
@@ -157,7 +156,7 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
*/
@Test
fun testAodUiNotShownIfInteractive() {
- `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(true)
+ `when`(dozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true)
`when`(powerManager.isInteractive).thenReturn(true)
val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java)
@@ -168,13 +167,4 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
verify(notificationPanelViewController, never()).showAodUi()
}
-
- @Test
- fun testNoAnimationPlaying_dozeParamsCanNotControlScreenOff() {
- `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(false)
-
- assertFalse(controller.shouldPlayUnlockedScreenOffAnimation())
- controller.startAnimation()
- assertFalse(controller.isAnimationPlaying())
- }
} \ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 0e9926590511..0ea087d6de0f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2749,6 +2749,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
private void updateWindowMagnificationConnectionIfNeeded(AccessibilityUserState userState) {
+ if (!mMagnificationController.supportWindowMagnification()) {
+ return;
+ }
final boolean connect = (userState.isShortcutMagnificationEnabledLocked()
|| userState.isDisplayMagnificationEnabledLocked())
&& (userState.getMagnificationCapabilitiesLocked()
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index a95820966926..fa32452f389e 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -303,7 +303,7 @@ public class FullScreenMagnificationController implements
public void onImeWindowVisibilityChanged(boolean shown) {
final Message m = PooledLambda.obtainMessage(
FullScreenMagnificationController::notifyImeWindowVisibilityChanged,
- FullScreenMagnificationController.this, shown);
+ FullScreenMagnificationController.this, mDisplayId, shown);
mControllerCtx.getHandler().sendMessage(m);
}
@@ -1215,11 +1215,12 @@ public class FullScreenMagnificationController implements
/**
* Notifies that the IME window visibility changed.
*
+ * @param displayId the logical display id
* @param shown {@code true} means the IME window shows on the screen. Otherwise it's
* hidden.
*/
- void notifyImeWindowVisibilityChanged(boolean shown) {
- mMagnificationInfoChangedCallback.onImeWindowVisibilityChanged(shown);
+ void notifyImeWindowVisibilityChanged(int displayId, boolean shown) {
+ mMagnificationInfoChangedCallback.onImeWindowVisibilityChanged(displayId, shown);
}
/**
@@ -1609,17 +1610,19 @@ public class FullScreenMagnificationController implements
* Called when the state of the magnification activation is changed.
* It is for the logging data of the magnification activation state.
*
- * @param displayId The logical display id.
+ * @param displayId the logical display id
* @param activated {@code true} if the magnification is activated, otherwise {@code false}.
*/
void onFullScreenMagnificationActivationState(int displayId, boolean activated);
/**
* Called when the IME window visibility changed.
+ *
+ * @param displayId the logical display id
* @param shown {@code true} means the IME window shows on the screen. Otherwise it's
* hidden.
*/
- void onImeWindowVisibilityChanged(boolean shown);
+ void onImeWindowVisibilityChanged(int displayId, boolean shown);
/**
* Called when the magnification spec changed.
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 09e82c787c90..62ba0c821f4e 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -17,6 +17,7 @@
package com.android.server.accessibility.magnification;
import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_WINDOW;
+import static android.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
@@ -38,6 +39,7 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.view.accessibility.MagnificationAnimationCallback;
import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
@@ -91,6 +93,8 @@ public class MagnificationController implements WindowMagnificationManager.Callb
private FullScreenMagnificationController mFullScreenMagnificationController;
private WindowMagnificationManager mWindowMagnificationMgr;
private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+ /** Whether the platform supports window magnification feature. */
+ private final boolean mSupportWindowMagnification;
@GuardedBy("mLock")
private int mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
@@ -99,7 +103,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb
// Track the active user to reset the magnification and get the associated user settings.
private @UserIdInt int mUserId = UserHandle.USER_SYSTEM;
@GuardedBy("mLock")
- private boolean mImeWindowVisible = false;
+ private final SparseBooleanArray mIsImeVisibleArray = new SparseBooleanArray();
private long mWindowModeEnabledTime = 0;
private long mFullScreenModeEnabledTime = 0;
@@ -129,6 +133,8 @@ public class MagnificationController implements WindowMagnificationManager.Callb
mScaleProvider = scaleProvider;
LocalServices.getService(WindowManagerInternal.class)
.getAccessibilityController().setUiChangesForAccessibilityCallbacks(this);
+ mSupportWindowMagnification = context.getPackageManager().hasSystemFeature(
+ FEATURE_WINDOW_MAGNIFICATION);
}
@VisibleForTesting
@@ -185,6 +191,11 @@ public class MagnificationController implements WindowMagnificationManager.Callb
}
}
+ /** Returns {@code true} if the platform supports window magnification feature. */
+ public boolean supportWindowMagnification() {
+ return mSupportWindowMagnification;
+ }
+
/**
* Transitions to the target Magnification mode with current center of the magnification mode
* if it is available.
@@ -377,7 +388,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb
setActivatedModeAndSwitchDelegate(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
mLastActivatedMode = mActivatedMode;
}
- logMagnificationModeWithImeOnIfNeeded();
+ logMagnificationModeWithImeOnIfNeeded(displayId);
disableFullScreenMagnificationIfNeeded(displayId);
} else {
logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
@@ -432,7 +443,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb
setActivatedModeAndSwitchDelegate(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
mLastActivatedMode = mActivatedMode;
}
- logMagnificationModeWithImeOnIfNeeded();
+ logMagnificationModeWithImeOnIfNeeded(displayId);
disableWindowMagnificationIfNeeded(displayId);
} else {
logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN,
@@ -454,12 +465,12 @@ public class MagnificationController implements WindowMagnificationManager.Callb
}
@Override
- public void onImeWindowVisibilityChanged(boolean shown) {
+ public void onImeWindowVisibilityChanged(int displayId, boolean shown) {
synchronized (mLock) {
- mImeWindowVisible = shown;
+ mIsImeVisibleArray.put(displayId, shown);
}
- getWindowMagnificationMgr().onImeWindowVisibilityChanged(shown);
- logMagnificationModeWithImeOnIfNeeded();
+ getWindowMagnificationMgr().onImeWindowVisibilityChanged(displayId, shown);
+ logMagnificationModeWithImeOnIfNeeded(displayId);
}
/**
@@ -575,11 +586,12 @@ public class MagnificationController implements WindowMagnificationManager.Callb
}
}
- private void logMagnificationModeWithImeOnIfNeeded() {
+ private void logMagnificationModeWithImeOnIfNeeded(int displayId) {
final int mode;
synchronized (mLock) {
- if (!mImeWindowVisible || mActivatedMode == ACCESSIBILITY_MAGNIFICATION_MODE_NONE) {
+ if (!mIsImeVisibleArray.get(displayId, false)
+ || mActivatedMode == ACCESSIBILITY_MAGNIFICATION_MODE_NONE) {
return;
}
mode = mActivatedMode;
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
index 175182c7a3bb..3e07b095fd29 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
@@ -396,6 +396,10 @@ public class MagnificationProcessor {
dumpTrackingTypingFocusEnabledState(pw, displayId, config.getMode());
}
+ pw.append(" SupportWindowMagnification="
+ + mController.supportWindowMagnification()).println();
+ pw.append(" WindowMagnificationConnectionState="
+ + mController.getWindowMagnificationMgr().getConnectionState()).println();
}
private int getIdOfLastServiceToMagnify(int mode, int displayId) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
index 25dcc2aea41b..041eece5ce48 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
@@ -133,6 +133,25 @@ class WindowMagnificationConnectionWrapper {
return true;
}
+ boolean moveWindowMagnifierToPosition(int displayId, float positionX, float positionY,
+ @Nullable MagnificationAnimationCallback callback) {
+ if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+ mTrace.logTrace(TAG + ".moveWindowMagnifierToPosition",
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "displayId=" + displayId
+ + ";positionX=" + positionX + ";positionY=" + positionY);
+ }
+ try {
+ mConnection.moveWindowMagnifierToPosition(displayId, positionX, positionY,
+ transformToRemoteCallback(callback, mTrace));
+ } catch (RemoteException e) {
+ if (DBG) {
+ Slog.e(TAG, "Error calling moveWindowMagnifierToPosition()", e);
+ }
+ return false;
+ }
+ return true;
+ }
+
boolean showMagnificationButton(int displayId, int magnificationMode) {
if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
mTrace.logTrace(TAG + ".showMagnificationButton",
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index 89910eac06c5..aeb1112ede5f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -36,8 +36,10 @@ import android.graphics.Region;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.view.MotionEvent;
import android.view.accessibility.IWindowMagnificationConnection;
import android.view.accessibility.IWindowMagnificationConnectionCallback;
@@ -87,6 +89,30 @@ public class WindowMagnificationManager implements
})
public @interface WindowPosition {}
+ /** Window magnification connection is connecting. */
+ private static final int CONNECTING = 0;
+ /** Window magnification connection is connected. */
+ private static final int CONNECTED = 1;
+ /** Window magnification connection is disconnecting. */
+ private static final int DISCONNECTING = 2;
+ /** Window magnification connection is disconnected. */
+ private static final int DISCONNECTED = 3;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"CONNECTION_STATE"}, value = {
+ CONNECTING,
+ CONNECTED,
+ DISCONNECTING,
+ DISCONNECTED
+ })
+ private @interface ConnectionState {
+ }
+
+ @ConnectionState
+ private int mConnectionState = DISCONNECTED;
+
+ private static final int WAIT_CONNECTION_TIMEOUT_MILLIS = 100;
+
private final Object mLock;
private final Context mContext;
@VisibleForTesting
@@ -99,6 +125,7 @@ public class WindowMagnificationManager implements
private SparseArray<WindowMagnifier> mWindowMagnifiers = new SparseArray<>();
// Whether the following typing focus feature for magnification is enabled.
private boolean mMagnificationFollowTypingEnabled = true;
+ private final SparseBooleanArray mIsImeVisibleArray = new SparseBooleanArray();
private boolean mReceiverRegistered = false;
@VisibleForTesting
@@ -178,7 +205,7 @@ public class WindowMagnificationManager implements
*/
public void setConnection(@Nullable IWindowMagnificationConnection connection) {
if (DBG) {
- Slog.d(TAG, "setConnection :" + connection);
+ Slog.d(TAG, "setConnection :" + connection + " ,mConnectionState=" + mConnectionState);
}
synchronized (mLock) {
// Reset connectionWrapper.
@@ -189,6 +216,13 @@ public class WindowMagnificationManager implements
}
mConnectionWrapper.unlinkToDeath(mConnectionCallback);
mConnectionWrapper = null;
+ // The connection is still connecting so it is no need to reset the
+ // connection state to disconnected.
+ // TODO b/220086369 will reset the connection immediately when requestConnection
+ // is called
+ if (mConnectionState != CONNECTING) {
+ setConnectionState(DISCONNECTED);
+ }
}
if (connection != null) {
mConnectionWrapper = new WindowMagnificationConnectionWrapper(connection, mTrace);
@@ -199,9 +233,13 @@ public class WindowMagnificationManager implements
mConnectionCallback = new ConnectionCallback();
mConnectionWrapper.linkToDeath(mConnectionCallback);
mConnectionWrapper.setConnectionCallback(mConnectionCallback);
+ setConnectionState(CONNECTED);
} catch (RemoteException e) {
Slog.e(TAG, "setConnection failed", e);
mConnectionWrapper = null;
+ setConnectionState(DISCONNECTED);
+ } finally {
+ mLock.notify();
}
}
}
@@ -229,10 +267,20 @@ public class WindowMagnificationManager implements
if (DBG) {
Slog.d(TAG, "requestConnection :" + connect);
}
+ if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+ mTrace.logTrace(TAG + ".requestWindowMagnificationConnection",
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "connect=" + connect);
+ }
synchronized (mLock) {
- if (connect == isConnected()) {
+ if ((connect && (mConnectionState == CONNECTED || mConnectionState == CONNECTING))
+ || (!connect && (mConnectionState == DISCONNECTED
+ || mConnectionState == DISCONNECTING))) {
+ Slog.w(TAG,
+ "requestConnection duplicated request: connect=" + connect
+ + " ,mConnectionState=" + mConnectionState);
return false;
}
+
if (connect) {
final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
if (!mReceiverRegistered) {
@@ -247,19 +295,42 @@ public class WindowMagnificationManager implements
}
}
}
- if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
- mTrace.logTrace(TAG + ".requestWindowMagnificationConnection",
- FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "connect=" + connect);
+ if (requestConnectionInternal(connect)) {
+ setConnectionState(connect ? CONNECTING : DISCONNECTING);
+ return true;
+ } else {
+ setConnectionState(DISCONNECTED);
+ return false;
}
+ }
+
+ private boolean requestConnectionInternal(boolean connect) {
final long identity = Binder.clearCallingIdentity();
try {
final StatusBarManagerInternal service = LocalServices.getService(
StatusBarManagerInternal.class);
- service.requestWindowMagnificationConnection(connect);
+ if (service != null) {
+ return service.requestWindowMagnificationConnection(connect);
+ }
} finally {
Binder.restoreCallingIdentity(identity);
}
- return true;
+ return false;
+ }
+
+ /**
+ * Returns window magnification connection state.
+ */
+ public int getConnectionState() {
+ return mConnectionState;
+ }
+
+ private void setConnectionState(@ConnectionState int state) {
+ if (DBG) {
+ Slog.d(TAG, "setConnectionState : state=" + state + " ,mConnectionState="
+ + mConnectionState);
+ }
+ mConnectionState = state;
}
/**
@@ -314,9 +385,13 @@ public class WindowMagnificationManager implements
float toCenterX = (float) (left + right) / 2;
float toCenterY = (float) (top + bottom) / 2;
- if (!isPositionInSourceBounds(displayId, toCenterX, toCenterY)
- && isTrackingTypingFocusEnabled(displayId)) {
- enableWindowMagnification(displayId, Float.NaN, toCenterX, toCenterY);
+ synchronized (mLock) {
+ if (mIsImeVisibleArray.get(displayId, false)
+ && !isPositionInSourceBounds(displayId, toCenterX, toCenterY)
+ && isTrackingTypingFocusEnabled(displayId)) {
+ moveWindowMagnifierToPositionInternal(displayId, toCenterX, toCenterY,
+ STUB_ANIMATION_CALLBACK);
+ }
}
}
@@ -357,7 +432,7 @@ public class WindowMagnificationManager implements
* @param displayId The logical display id.
* @param trackingTypingFocusEnabled Enabled or disable the function of tracking typing focus.
*/
- private void setTrackingTypingFocusEnabled(int displayId, boolean trackingTypingFocusEnabled) {
+ void setTrackingTypingFocusEnabled(int displayId, boolean trackingTypingFocusEnabled) {
synchronized (mLock) {
WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
if (magnifier == null) {
@@ -384,7 +459,8 @@ public class WindowMagnificationManager implements
*
* @param shown {@code true} means the IME window shows on the screen. Otherwise, it's hidden.
*/
- void onImeWindowVisibilityChanged(boolean shown) {
+ void onImeWindowVisibilityChanged(int displayId, boolean shown) {
+ mIsImeVisibleArray.put(displayId, shown);
if (shown) {
enableAllTrackingTypingFocus();
}
@@ -500,8 +576,11 @@ public class WindowMagnificationManager implements
animationCallback, windowPosition, id);
}
- if (enabled && !previousEnabled) {
- mCallback.onWindowMagnificationActivationState(displayId, true);
+ if (enabled) {
+ setTrackingTypingFocusEnabled(displayId, true);
+ if (!previousEnabled) {
+ mCallback.onWindowMagnificationActivationState(displayId, true);
+ }
}
return enabled;
}
@@ -563,14 +642,13 @@ public class WindowMagnificationManager implements
}
}
+ @GuardedBy("mLock")
boolean isPositionInSourceBounds(int displayId, float x, float y) {
- synchronized (mLock) {
- WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
- if (magnifier == null) {
- return false;
- }
- return magnifier.isPositionInSourceBounds(x, y);
+ WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
+ if (magnifier == null) {
+ return false;
}
+ return magnifier.isPositionInSourceBounds(x, y);
}
/**
@@ -829,10 +907,10 @@ public class WindowMagnificationManager implements
}
@Override
- public void onDrag(int displayId) {
+ public void onMove(int displayId) {
if (mTrace.isA11yTracingEnabledForTypes(
FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
- mTrace.logTrace(TAG + "ConnectionCallback.onDrag",
+ mTrace.logTrace(TAG + "ConnectionCallback.onMove",
FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
"displayId=" + displayId);
}
@@ -849,6 +927,7 @@ public class WindowMagnificationManager implements
mConnectionWrapper.unlinkToDeath(this);
mConnectionWrapper = null;
mConnectionCallback = null;
+ setConnectionState(DISCONNECTED);
resetWindowMagnifiers();
}
}
@@ -884,7 +963,6 @@ public class WindowMagnificationManager implements
mWindowMagnificationManager = windowMagnificationManager;
}
- @GuardedBy("mLock")
boolean enableWindowMagnificationInternal(float scale, float centerX, float centerY,
@Nullable MagnificationAnimationCallback animationCallback,
@WindowPosition int windowPosition, int id) {
@@ -962,7 +1040,6 @@ public class WindowMagnificationManager implements
return mIdOfLastServiceToControl;
}
- @GuardedBy("mLock")
int pointersInWindow(MotionEvent motionEvent) {
int count = 0;
final int pointerCount = motionEvent.getPointerCount();
@@ -1025,11 +1102,22 @@ public class WindowMagnificationManager implements
float centerY, float magnificationFrameOffsetRatioX,
float magnificationFrameOffsetRatioY,
MagnificationAnimationCallback animationCallback) {
+ // Wait for the connection with a timeout.
+ final long endMillis = SystemClock.uptimeMillis() + WAIT_CONNECTION_TIMEOUT_MILLIS;
+ while (mConnectionState == CONNECTING && (SystemClock.uptimeMillis() < endMillis)) {
+ try {
+ mLock.wait(endMillis - SystemClock.uptimeMillis());
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
if (mConnectionWrapper == null) {
- Slog.w(TAG, "enableWindowMagnificationInternal mConnectionWrapper is null");
+ Slog.w(TAG,
+ "enableWindowMagnificationInternal mConnectionWrapper is null. "
+ + "mConnectionState=" + mConnectionState);
return false;
}
- return mConnectionWrapper.enableWindowMagnification(
+ return mConnectionWrapper.enableWindowMagnification(
displayId, scale, centerX, centerY,
magnificationFrameOffsetRatioX, magnificationFrameOffsetRatioY,
animationCallback);
@@ -1050,8 +1138,16 @@ public class WindowMagnificationManager implements
displayId, animationCallback);
}
+ @GuardedBy("mLock")
private boolean moveWindowMagnifierInternal(int displayId, float offsetX, float offsetY) {
return mConnectionWrapper != null && mConnectionWrapper.moveWindowMagnifier(
displayId, offsetX, offsetY);
}
+
+ @GuardedBy("mLock")
+ private boolean moveWindowMagnifierToPositionInternal(int displayId, float positionX,
+ float positionY, MagnificationAnimationCallback animationCallback) {
+ return mConnectionWrapper != null && mConnectionWrapper.moveWindowMagnifierToPosition(
+ displayId, positionX, positionY, animationCallback);
+ }
}
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 111bd340a3d1..e6953f0032c7 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -657,6 +657,11 @@ public abstract class PackageManagerInternal {
public abstract void notifyPackageUse(String packageName, int reason);
/**
+ * Notify the package is force stopped.
+ */
+ public abstract void onPackageProcessKilledForUninstall(String packageName);
+
+ /**
* Returns a package object for the given package name.
*/
public abstract @Nullable AndroidPackage getPackage(@NonNull String packageName);
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 6986d3bbe585..1f8ef8226c32 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -18,14 +18,22 @@ package com.android.server;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.ModuleInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
+import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemProperties;
@@ -42,6 +50,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Executors;
import java.util.stream.Collectors;
/**
@@ -49,6 +58,7 @@ import java.util.stream.Collectors;
*/
public class BinaryTransparencyService extends SystemService {
private static final String TAG = "TransparencyService";
+ private static final String EXTRA_SERVICE = "service";
@VisibleForTesting
static final String VBMETA_DIGEST_UNINITIALIZED = "vbmeta-digest-uninitialized";
@@ -365,10 +375,80 @@ public class BinaryTransparencyService extends SystemService {
// we are only interested in doing things at PHASE_BOOT_COMPLETED
if (phase == PHASE_BOOT_COMPLETED) {
- // due to potentially long computation that holds up boot time, apex sha computations
- // are deferred to first call
Slog.i(TAG, "Boot completed. Getting VBMeta Digest.");
getVBMetaDigestInformation();
+
+ // due to potentially long computation that holds up boot time, computations for
+ // SHA256 digests of APEX and Module packages are scheduled here,
+ // but only executed when device is idle.
+ Slog.i(TAG, "Scheduling APEX and Module measurements to be updated.");
+ UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext,
+ BinaryTransparencyService.this);
+ }
+ }
+
+ /**
+ * JobService to update binary measurements and update internal cache.
+ */
+ public static class UpdateMeasurementsJobService extends JobService {
+ private static final int COMPUTE_APEX_MODULE_SHA256_JOB_ID =
+ BinaryTransparencyService.UpdateMeasurementsJobService.class.hashCode();
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ Slog.d(TAG, "Job to update binary measurements started.");
+ if (params.getJobId() != COMPUTE_APEX_MODULE_SHA256_JOB_ID) {
+ return false;
+ }
+
+ // we'll still update the measurements via threads to be mindful of low-end devices
+ // where this operation might take longer than expected, and so that we don't block
+ // system_server's main thread.
+ Executors.defaultThreadFactory().newThread(() -> {
+ // since we can't call updateBinaryMeasurements() directly, calling
+ // getApexInfo() achieves the same effect, and we simply discard the return
+ // value
+
+ IBinder b = ServiceManager.getService(Context.BINARY_TRANSPARENCY_SERVICE);
+ IBinaryTransparencyService iBtsService =
+ IBinaryTransparencyService.Stub.asInterface(b);
+ try {
+ iBtsService.getApexInfo();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Updating binary measurements was interrupted.", e);
+ return;
+ }
+ jobFinished(params, false);
+ }).start();
+
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return false;
+ }
+
+ @SuppressLint("DefaultLocale")
+ static void scheduleBinaryMeasurements(Context context, BinaryTransparencyService service) {
+ Slog.i(TAG, "Scheduling APEX & Module SHA256 digest computation job");
+ final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ if (jobScheduler == null) {
+ Slog.e(TAG, "Failed to obtain an instance of JobScheduler.");
+ return;
+ }
+
+ final JobInfo jobInfo = new JobInfo.Builder(COMPUTE_APEX_MODULE_SHA256_JOB_ID,
+ new ComponentName(context, UpdateMeasurementsJobService.class))
+ .setRequiresDeviceIdle(true)
+ .build();
+ if (jobScheduler.schedule(jobInfo) != JobScheduler.RESULT_SUCCESS) {
+ Slog.e(TAG, "Failed to schedule job to update binary measurements.");
+ return;
+ }
+ Slog.d(TAG, String.format(
+ "Job %d to update binary measurements scheduled successfully.",
+ COMPUTE_APEX_MODULE_SHA256_JOB_ID));
}
}
@@ -380,7 +460,7 @@ public class BinaryTransparencyService extends SystemService {
@NonNull
private List<PackageInfo> getInstalledApexs() {
- List<PackageInfo> results = new ArrayList<PackageInfo>();
+ List<PackageInfo> results = new ArrayList<>();
PackageManager pm = mContext.getPackageManager();
if (pm == null) {
Slog.e(TAG, "Error obtaining an instance of PackageManager.");
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateService.java b/services/core/java/com/android/server/NetworkTimeUpdateService.java
index fcde533fe05c..1e534b7c1515 100644
--- a/services/core/java/com/android/server/NetworkTimeUpdateService.java
+++ b/services/core/java/com/android/server/NetworkTimeUpdateService.java
@@ -36,12 +36,15 @@ import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.os.SystemClock;
import android.os.TimestampedValue;
import android.provider.Settings;
import android.util.LocalLog;
import android.util.Log;
import android.util.NtpTrustedTime;
+import android.util.NtpTrustedTime.TimeResult;
import android.util.TimeUtils;
import com.android.internal.util.DumpUtils;
@@ -152,6 +155,42 @@ public class NetworkTimeUpdateService extends Binder {
}, new IntentFilter(ACTION_POLL));
}
+ /**
+ * Clears the cached NTP time. For use during tests to simulate when no NTP time is available.
+ *
+ * <p>This operation takes place in the calling thread rather than the service's handler thread.
+ */
+ void clearTimeForTests() {
+ mContext.enforceCallingPermission(
+ android.Manifest.permission.SET_TIME, "clear latest network time");
+
+ mTime.clearCachedTimeResult();
+
+ mLocalLog.log("clearTimeForTests");
+ }
+
+ /**
+ * Forces the service to refresh the NTP time.
+ *
+ * <p>This operation takes place in the calling thread rather than the service's handler thread.
+ * This method does not affect currently scheduled refreshes. If the NTP request is successful
+ * it will make an (asynchronously handled) suggestion to the time detector.
+ */
+ boolean forceRefreshForTests() {
+ mContext.enforceCallingPermission(
+ android.Manifest.permission.SET_TIME, "force network time refresh");
+
+ boolean success = mTime.forceRefresh();
+ mLocalLog.log("forceRefreshForTests: success=" + success);
+
+ if (success) {
+ makeNetworkTimeSuggestion(mTime.getCachedTimeResult(),
+ "Origin: NetworkTimeUpdateService: forceRefreshForTests");
+ }
+
+ return success;
+ }
+
private void onPollNetworkTime(int event) {
// If we don't have any default network, don't bother.
if (mDefaultNetwork == null) return;
@@ -193,12 +232,8 @@ public class NetworkTimeUpdateService extends Binder {
resetAlarm(mPollingIntervalMs
- cachedNtpResult.getAgeMillis(currentElapsedRealtimeMillis));
- // Suggest the time to the time detector. It may choose use it to set the system clock.
- TimestampedValue<Long> timeSignal = new TimestampedValue<>(
- cachedNtpResult.getElapsedRealtimeMillis(), cachedNtpResult.getTimeMillis());
- NetworkTimeSuggestion timeSuggestion = new NetworkTimeSuggestion(timeSignal);
- timeSuggestion.addDebugInfo("Origin: NetworkTimeUpdateService. event=" + event);
- mTimeDetector.suggestNetworkTime(timeSuggestion);
+ makeNetworkTimeSuggestion(cachedNtpResult,
+ "Origin: NetworkTimeUpdateService. event=" + event);
} else {
// No fresh fix; schedule retry
mTryAgainCounter++;
@@ -217,6 +252,15 @@ public class NetworkTimeUpdateService extends Binder {
}
}
+ /** Suggests the time to the time detector. It may choose use it to set the system clock. */
+ private void makeNetworkTimeSuggestion(TimeResult ntpResult, String debugInfo) {
+ TimestampedValue<Long> timeSignal = new TimestampedValue<>(
+ ntpResult.getElapsedRealtimeMillis(), ntpResult.getTimeMillis());
+ NetworkTimeSuggestion timeSuggestion = new NetworkTimeSuggestion(timeSignal);
+ timeSuggestion.addDebugInfo(debugInfo);
+ mTimeDetector.suggestNetworkTime(timeSuggestion);
+ }
+
/**
* Cancel old alarm and starts a new one for the specified interval.
*
@@ -320,4 +364,11 @@ public class NetworkTimeUpdateService extends Binder {
mLocalLog.dump(fd, pw, args);
pw.println();
}
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+ new NetworkTimeUpdateServiceShellCommand(this).exec(
+ this, in, out, err, args, callback, resultReceiver);
+ }
}
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateServiceShellCommand.java b/services/core/java/com/android/server/NetworkTimeUpdateServiceShellCommand.java
new file mode 100644
index 000000000000..dc93023d82c5
--- /dev/null
+++ b/services/core/java/com/android/server/NetworkTimeUpdateServiceShellCommand.java
@@ -0,0 +1,90 @@
+/*
+ * 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.server;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/** Implements the shell command interface for {@link NetworkTimeUpdateService}. */
+class NetworkTimeUpdateServiceShellCommand extends ShellCommand {
+
+ /**
+ * The name of the service.
+ */
+ private static final String SHELL_COMMAND_SERVICE_NAME = "network_time_update_service";
+
+ /**
+ * A shell command that clears the time signal received from the network.
+ */
+ private static final String SHELL_COMMAND_CLEAR_TIME = "clear_time";
+
+ /**
+ * A shell command that forces the time signal to be refreshed from the network.
+ */
+ private static final String SHELL_COMMAND_FORCE_REFRESH = "force_refresh";
+
+ @NonNull
+ private final NetworkTimeUpdateService mNetworkTimeUpdateService;
+
+ NetworkTimeUpdateServiceShellCommand(NetworkTimeUpdateService networkTimeUpdateService) {
+ mNetworkTimeUpdateService = Objects.requireNonNull(networkTimeUpdateService);
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+
+ switch (cmd) {
+ case SHELL_COMMAND_CLEAR_TIME:
+ return runClearTime();
+ case SHELL_COMMAND_FORCE_REFRESH:
+ return runForceRefresh();
+ default: {
+ return handleDefaultCommands(cmd);
+ }
+ }
+ }
+
+ private int runClearTime() {
+ mNetworkTimeUpdateService.clearTimeForTests();
+ return 0;
+ }
+
+ private int runForceRefresh() {
+ boolean success = mNetworkTimeUpdateService.forceRefreshForTests();
+ getOutPrintWriter().println(success);
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.printf("Network Time Update Service (%s) commands:\n", SHELL_COMMAND_SERVICE_NAME);
+ pw.printf(" help\n");
+ pw.printf(" Print this help text.\n");
+ pw.printf(" %s\n", SHELL_COMMAND_CLEAR_TIME);
+ pw.printf(" Clears the latest time.\n");
+ pw.printf(" %s\n", SHELL_COMMAND_FORCE_REFRESH);
+ pw.printf(" Refreshes the latest time. Prints whether it was successful.\n");
+ pw.println();
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 38f6e6d9f165..d4ad718fbe73 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -4139,7 +4139,8 @@ public final class ActiveServices {
final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;
final String procName = r.processName;
- HostingRecord hostingRecord = new HostingRecord("service", r.instanceName);
+ HostingRecord hostingRecord = new HostingRecord("service", r.instanceName,
+ r.definingPackageName, r.definingUid, r.serviceInfo.processName);
ProcessRecord app;
if (!isolated) {
@@ -4177,11 +4178,12 @@ public final class ActiveServices {
app = r.isolationHostProc;
if (WebViewZygote.isMultiprocessEnabled()
&& r.serviceInfo.packageName.equals(WebViewZygote.getPackageName())) {
- hostingRecord = HostingRecord.byWebviewZygote(r.instanceName);
+ hostingRecord = HostingRecord.byWebviewZygote(r.instanceName, r.definingPackageName,
+ r.definingUid, r.serviceInfo.processName);
}
if ((r.serviceInfo.flags & ServiceInfo.FLAG_USE_APP_ZYGOTE) != 0) {
hostingRecord = HostingRecord.byAppZygote(r.instanceName, r.definingPackageName,
- r.definingUid);
+ r.definingUid, r.serviceInfo.processName);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3302cfb9bd66..47f9fd535801 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -12426,10 +12426,19 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- synchronized(this) {
- return mServices.bindServiceLocked(caller, token, service, resolvedType, connection,
- flags, instanceName, isSdkSandboxService, sdkSandboxClientdAppUid,
- callingPackage, userId);
+ try {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ final ComponentName cn = service.getComponent();
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindService:"
+ + (cn != null ? cn.toShortString() : service.getAction()));
+ }
+ synchronized (this) {
+ return mServices.bindServiceLocked(caller, token, service, resolvedType, connection,
+ flags, instanceName, isSdkSandboxService, sdkSandboxClientdAppUid,
+ callingPackage, userId);
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -13586,6 +13595,8 @@ public class ActivityManagerService extends IActivityManager.Stub
intent.getIntExtra(Intent.EXTRA_UID, -1)),
false, true, true, false, fullUninstall, userId,
removed ? "pkg removed" : "pkg changed");
+ getPackageManagerInternal()
+ .onPackageProcessKilledForUninstall(ssp);
} else {
// Kill any app zygotes always, since they can't fork new
// processes with references to the old code
diff --git a/services/core/java/com/android/server/am/HostingRecord.java b/services/core/java/com/android/server/am/HostingRecord.java
index 6bb5def26b9d..bbf586123e1c 100644
--- a/services/core/java/com/android/server/am/HostingRecord.java
+++ b/services/core/java/com/android/server/am/HostingRecord.java
@@ -56,19 +56,27 @@ public final class HostingRecord {
private final String mDefiningPackageName;
private final int mDefiningUid;
private final boolean mIsTopApp;
+ private final String mDefiningProcessName;
public HostingRecord(String hostingType) {
this(hostingType, null /* hostingName */, REGULAR_ZYGOTE, null /* definingPackageName */,
- -1 /* mDefiningUid */, false /* isTopApp */);
+ -1 /* mDefiningUid */, false /* isTopApp */, null /* definingProcessName */);
}
public HostingRecord(String hostingType, ComponentName hostingName) {
this(hostingType, hostingName, REGULAR_ZYGOTE);
}
+ public HostingRecord(String hostingType, ComponentName hostingName, String definingPackageName,
+ int definingUid, String definingProcessName) {
+ this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE, definingPackageName,
+ definingUid, false /* isTopApp */, definingProcessName);
+ }
+
public HostingRecord(String hostingType, ComponentName hostingName, boolean isTopApp) {
this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE,
- null /* definingPackageName */, -1 /* mDefiningUid */, isTopApp /* isTopApp */);
+ null /* definingPackageName */, -1 /* mDefiningUid */, isTopApp /* isTopApp */,
+ null /* definingProcessName */);
}
public HostingRecord(String hostingType, String hostingName) {
@@ -81,17 +89,19 @@ public final class HostingRecord {
private HostingRecord(String hostingType, String hostingName, int hostingZygote) {
this(hostingType, hostingName, hostingZygote, null /* definingPackageName */,
- -1 /* mDefiningUid */, false /* isTopApp */);
+ -1 /* mDefiningUid */, false /* isTopApp */, null /* definingProcessName */);
}
private HostingRecord(String hostingType, String hostingName, int hostingZygote,
- String definingPackageName, int definingUid, boolean isTopApp) {
+ String definingPackageName, int definingUid, boolean isTopApp,
+ String definingProcessName) {
mHostingType = hostingType;
mHostingName = hostingName;
mHostingZygote = hostingZygote;
mDefiningPackageName = definingPackageName;
mDefiningUid = definingUid;
mIsTopApp = isTopApp;
+ mDefiningProcessName = definingProcessName;
}
public String getType() {
@@ -127,12 +137,24 @@ public final class HostingRecord {
}
/**
+ * Returns the processName of the component we want to start as specified in the defining app's
+ * manifest.
+ *
+ * @return the processName of the process in the hosting application
+ */
+ public String getDefiningProcessName() {
+ return mDefiningProcessName;
+ }
+
+ /**
* Creates a HostingRecord for a process that must spawn from the webview zygote
* @param hostingName name of the component to be hosted in this process
* @return The constructed HostingRecord
*/
- public static HostingRecord byWebviewZygote(ComponentName hostingName) {
- return new HostingRecord("", hostingName.toShortString(), WEBVIEW_ZYGOTE);
+ public static HostingRecord byWebviewZygote(ComponentName hostingName,
+ String definingPackageName, int definingUid, String definingProcessName) {
+ return new HostingRecord("", hostingName.toShortString(), WEBVIEW_ZYGOTE,
+ definingPackageName, definingUid, false /* isTopApp */, definingProcessName);
}
/**
@@ -143,9 +165,9 @@ public final class HostingRecord {
* @return The constructed HostingRecord
*/
public static HostingRecord byAppZygote(ComponentName hostingName, String definingPackageName,
- int definingUid) {
+ int definingUid, String definingProcessName) {
return new HostingRecord("", hostingName.toShortString(), APP_ZYGOTE,
- definingPackageName, definingUid, false /* isTopApp */);
+ definingPackageName, definingUid, false /* isTopApp */, definingProcessName);
}
/**
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 41cd61da022a..2c2e7c40c9c3 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -70,7 +70,6 @@ import android.app.IApplicationThread;
import android.app.IProcessObserver;
import android.app.IUidObserver;
import android.compat.annotation.ChangeId;
-import android.compat.annotation.Disabled;
import android.compat.annotation.EnabledAfter;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -363,59 +362,6 @@ public final class ProcessList {
private static final long LMKD_RECONNECT_DELAY_MS = 1000;
/**
- * Native heap allocations will now have a non-zero tag in the most significant byte.
- * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged
- * Pointers</a>
- */
- @ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
- private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id.
-
- /**
- * Native heap allocations in AppZygote process and its descendants will now have a
- * non-zero tag in the most significant byte.
- * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged
- * Pointers</a>
- */
- @ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S)
- private static final long NATIVE_HEAP_POINTER_TAGGING_APP_ZYGOTE = 207557677;
-
- /**
- * Enable asynchronous (ASYNC) memory tag checking in this process. This
- * flag will only have an effect on hardware supporting the ARM Memory
- * Tagging Extension (MTE).
- */
- @ChangeId
- @Disabled
- private static final long NATIVE_MEMTAG_ASYNC = 135772972; // This is a bug id.
-
- /**
- * Enable synchronous (SYNC) memory tag checking in this process. This flag
- * will only have an effect on hardware supporting the ARM Memory Tagging
- * Extension (MTE). If both NATIVE_MEMTAG_ASYNC and this option is selected,
- * this option takes preference and MTE is enabled in SYNC mode.
- */
- @ChangeId
- @Disabled
- private static final long NATIVE_MEMTAG_SYNC = 177438394; // This is a bug id.
-
- /**
- * Enable automatic zero-initialization of native heap memory allocations.
- */
- @ChangeId
- @Disabled
- private static final long NATIVE_HEAP_ZERO_INIT = 178038272; // This is a bug id.
-
- /**
- * Enable sampled memory bug detection in the app.
- * @see <a href="https://source.android.com/devices/tech/debug/gwp-asan">GWP-ASan</a>.
- */
- @ChangeId
- @Disabled
- private static final long GWP_ASAN = 135634846; // This is a bug id.
-
- /**
* Apps have no access to the private data directories of any other app, even if the other
* app has made them world-readable.
*/
@@ -1681,136 +1627,6 @@ public final class ProcessList {
return gidArray;
}
- private int memtagModeToZygoteMemtagLevel(int memtagMode) {
- switch (memtagMode) {
- case ApplicationInfo.MEMTAG_ASYNC:
- return Zygote.MEMORY_TAG_LEVEL_ASYNC;
- case ApplicationInfo.MEMTAG_SYNC:
- return Zygote.MEMORY_TAG_LEVEL_SYNC;
- default:
- return Zygote.MEMORY_TAG_LEVEL_NONE;
- }
- }
-
- // Returns the requested memory tagging level.
- private int getRequestedMemtagLevel(ProcessRecord app) {
- // Look at the process attribute first.
- if (app.processInfo != null
- && app.processInfo.memtagMode != ApplicationInfo.MEMTAG_DEFAULT) {
- return memtagModeToZygoteMemtagLevel(app.processInfo.memtagMode);
- }
-
- // Then at the application attribute.
- if (app.info.getMemtagMode() != ApplicationInfo.MEMTAG_DEFAULT) {
- return memtagModeToZygoteMemtagLevel(app.info.getMemtagMode());
- }
-
- if (mPlatformCompat.isChangeEnabled(NATIVE_MEMTAG_SYNC, app.info)) {
- return Zygote.MEMORY_TAG_LEVEL_SYNC;
- }
-
- if (mPlatformCompat.isChangeEnabled(NATIVE_MEMTAG_ASYNC, app.info)) {
- return Zygote.MEMORY_TAG_LEVEL_ASYNC;
- }
-
- // Check to ensure the app hasn't explicitly opted-out of TBI via. the manifest attribute.
- if (!app.info.allowsNativeHeapPointerTagging()) {
- return Zygote.MEMORY_TAG_LEVEL_NONE;
- }
-
- String defaultLevel = SystemProperties.get("persist.arm64.memtag.app_default");
- if ("sync".equals(defaultLevel)) {
- return Zygote.MEMORY_TAG_LEVEL_SYNC;
- } else if ("async".equals(defaultLevel)) {
- return Zygote.MEMORY_TAG_LEVEL_ASYNC;
- }
-
- // Check to see that the compat feature for TBI is enabled.
- if (mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) {
- return Zygote.MEMORY_TAG_LEVEL_TBI;
- }
-
- return Zygote.MEMORY_TAG_LEVEL_NONE;
- }
-
- private int decideTaggingLevel(ProcessRecord app) {
- // Get the desired tagging level (app manifest + compat features).
- int level = getRequestedMemtagLevel(app);
-
- // Take into account the hardware capabilities.
- if (Zygote.nativeSupportsMemoryTagging()) {
- // MTE devices can not do TBI, because the Zygote process already has live MTE
- // allocations. Downgrade TBI to NONE.
- if (level == Zygote.MEMORY_TAG_LEVEL_TBI) {
- level = Zygote.MEMORY_TAG_LEVEL_NONE;
- }
- } else if (Zygote.nativeSupportsTaggedPointers()) {
- // TBI-but-not-MTE devices downgrade MTE modes to TBI.
- // The idea is that if an app opts into full hardware tagging (MTE), it must be ok with
- // the "fake" pointer tagging (TBI).
- if (level == Zygote.MEMORY_TAG_LEVEL_ASYNC || level == Zygote.MEMORY_TAG_LEVEL_SYNC) {
- level = Zygote.MEMORY_TAG_LEVEL_TBI;
- }
- } else {
- // Otherwise disable all tagging.
- level = Zygote.MEMORY_TAG_LEVEL_NONE;
- }
-
- return level;
- }
-
- private int decideTaggingLevelForAppZygote(ProcessRecord app) {
- int level = decideTaggingLevel(app);
- // TBI ("fake" pointer tagging) in AppZygote is controlled by a separate compat feature.
- if (!mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING_APP_ZYGOTE, app.info)
- && level == Zygote.MEMORY_TAG_LEVEL_TBI) {
- level = Zygote.MEMORY_TAG_LEVEL_NONE;
- }
- return level;
- }
-
- private int decideGwpAsanLevel(ProcessRecord app) {
- // Look at the process attribute first.
- if (app.processInfo != null
- && app.processInfo.gwpAsanMode != ApplicationInfo.GWP_ASAN_DEFAULT) {
- return app.processInfo.gwpAsanMode == ApplicationInfo.GWP_ASAN_ALWAYS
- ? Zygote.GWP_ASAN_LEVEL_ALWAYS
- : Zygote.GWP_ASAN_LEVEL_NEVER;
- }
- // Then at the application attribute.
- if (app.info.getGwpAsanMode() != ApplicationInfo.GWP_ASAN_DEFAULT) {
- return app.info.getGwpAsanMode() == ApplicationInfo.GWP_ASAN_ALWAYS
- ? Zygote.GWP_ASAN_LEVEL_ALWAYS
- : Zygote.GWP_ASAN_LEVEL_NEVER;
- }
- // If the app does not specify gwpAsanMode, the default behavior is lottery among the
- // system apps, and disabled for user apps, unless overwritten by the compat feature.
- if (mPlatformCompat.isChangeEnabled(GWP_ASAN, app.info)) {
- return Zygote.GWP_ASAN_LEVEL_ALWAYS;
- }
- if ((app.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- return Zygote.GWP_ASAN_LEVEL_LOTTERY;
- }
- return Zygote.GWP_ASAN_LEVEL_NEVER;
- }
-
- private boolean enableNativeHeapZeroInit(ProcessRecord app) {
- // Look at the process attribute first.
- if (app.processInfo != null
- && app.processInfo.nativeHeapZeroInitialized != ApplicationInfo.ZEROINIT_DEFAULT) {
- return app.processInfo.nativeHeapZeroInitialized == ApplicationInfo.ZEROINIT_ENABLED;
- }
- // Then at the application attribute.
- if (app.info.getNativeHeapZeroInitialized() != ApplicationInfo.ZEROINIT_DEFAULT) {
- return app.info.getNativeHeapZeroInitialized() == ApplicationInfo.ZEROINIT_ENABLED;
- }
- // Compat feature last.
- if (mPlatformCompat.isChangeEnabled(NATIVE_HEAP_ZERO_INIT, app.info)) {
- return true;
- }
- return false;
- }
-
/**
* @return {@code true} if process start is successful, false otherwise.
*/
@@ -1992,8 +1808,6 @@ public final class ProcessList {
runtimeFlags |= Zygote.USE_APP_IMAGE_STARTUP_CACHE;
}
- runtimeFlags |= decideGwpAsanLevel(app);
-
String invokeWith = null;
if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
// Debuggable apps may include a wrapper script with their library directory.
@@ -2024,23 +1838,21 @@ public final class ProcessList {
app.setRequiredAbi(requiredAbi);
app.setInstructionSet(instructionSet);
- // If instructionSet is non-null, this indicates that the system_server is spawning a
- // process with an ISA that may be different from its own. System (kernel and hardware)
- // compatibility for these features is checked in the decideTaggingLevel in the
- // system_server process (not the child process). As both MTE and TBI are only supported
- // in aarch64, we can simply ensure that the new process is also aarch64. This prevents
- // the mismatch where a 64-bit system server spawns a 32-bit child that thinks it should
- // enable some tagging variant. Theoretically, a 32-bit system server could exist that
- // spawns 64-bit processes, in which case the new process won't get any tagging. This is
- // fine as we haven't seen this configuration in practice, and we can reasonable assume
- // that if tagging is desired, the system server will be 64-bit.
- if (instructionSet == null || instructionSet.equals("arm64")) {
- runtimeFlags |= decideTaggingLevel(app);
+ // If this was an external service, the package name and uid in the passed in
+ // ApplicationInfo have been changed to match those of the calling package;
+ // that will incorrectly apply compat feature overrides for the calling package instead
+ // of the defining one.
+ ApplicationInfo definingAppInfo;
+ if (hostingRecord.getDefiningPackageName() != null) {
+ definingAppInfo = new ApplicationInfo(app.info);
+ definingAppInfo.packageName = hostingRecord.getDefiningPackageName();
+ definingAppInfo.uid = uid;
+ } else {
+ definingAppInfo = app.info;
}
- if (enableNativeHeapZeroInit(app)) {
- runtimeFlags |= Zygote.NATIVE_HEAP_ZERO_INIT;
- }
+ runtimeFlags |= Zygote.getMemorySafetyRuntimeFlags(
+ definingAppInfo, app.processInfo, instructionSet, mPlatformCompat);
// the per-user SELinux context must be set
if (TextUtils.isEmpty(app.info.seInfoUser)) {
@@ -2299,8 +2111,7 @@ public final class ProcessList {
// not the calling one.
appInfo.packageName = app.getHostingRecord().getDefiningPackageName();
appInfo.uid = uid;
- int runtimeFlags = decideTaggingLevelForAppZygote(app);
- appZygote = new AppZygote(appInfo, uid, firstUid, lastUid, runtimeFlags);
+ appZygote = new AppZygote(appInfo, app.processInfo, uid, firstUid, lastUid);
mAppZygotes.put(app.info.processName, uid, appZygote);
zygoteProcessList = new ArrayList<ProcessRecord>();
mAppZygoteProcesses.put(appZygote, zygoteProcessList);
@@ -3158,7 +2969,8 @@ public final class ProcessList {
FrameworkStatsLog.write(FrameworkStatsLog.ISOLATED_UID_CHANGED, info.uid, uid,
FrameworkStatsLog.ISOLATED_UID_CHANGED__EVENT__CREATED);
}
- final ProcessRecord r = new ProcessRecord(mService, info, proc, uid);
+ final ProcessRecord r = new ProcessRecord(mService, info, proc, uid,
+ hostingRecord.getDefiningUid(), hostingRecord.getDefiningProcessName());
final ProcessStateRecord state = r.mState;
if (!mService.mBooted && !mService.mBooting
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index be187e21db47..7672d10e27bd 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -492,24 +492,33 @@ class ProcessRecord implements WindowProcessListener {
ProcessRecord(ActivityManagerService _service, ApplicationInfo _info, String _processName,
int _uid) {
+ this(_service, _info, _processName, _uid, -1, null);
+ }
+
+ ProcessRecord(ActivityManagerService _service, ApplicationInfo _info, String _processName,
+ int _uid, int _definingUid, String _definingProcessName) {
mService = _service;
mProcLock = _service.mProcLock;
info = _info;
ProcessInfo procInfo = null;
if (_service.mPackageManagerInt != null) {
- ArrayMap<String, ProcessInfo> processes =
- _service.mPackageManagerInt.getProcessesForUid(_uid);
- if (processes != null) {
- procInfo = processes.get(_processName);
- if (procInfo != null && procInfo.deniedPermissions == null
- && procInfo.gwpAsanMode == ApplicationInfo.GWP_ASAN_DEFAULT
- && procInfo.memtagMode == ApplicationInfo.MEMTAG_DEFAULT
- && procInfo.nativeHeapZeroInitialized == ApplicationInfo.ZEROINIT_DEFAULT) {
- // If this process hasn't asked for permissions to be denied, or for a
- // non-default GwpAsan mode, or any other non-default setting, then we don't
- // care about it.
- procInfo = null;
- }
+ if (_definingUid > 0) {
+ ArrayMap<String, ProcessInfo> processes =
+ _service.mPackageManagerInt.getProcessesForUid(_definingUid);
+ if (processes != null) procInfo = processes.get(_definingProcessName);
+ } else {
+ ArrayMap<String, ProcessInfo> processes =
+ _service.mPackageManagerInt.getProcessesForUid(_uid);
+ if (processes != null) procInfo = processes.get(_processName);
+ }
+ if (procInfo != null && procInfo.deniedPermissions == null
+ && procInfo.gwpAsanMode == ApplicationInfo.GWP_ASAN_DEFAULT
+ && procInfo.memtagMode == ApplicationInfo.MEMTAG_DEFAULT
+ && procInfo.nativeHeapZeroInitialized == ApplicationInfo.ZEROINIT_DEFAULT) {
+ // If this process hasn't asked for permissions to be denied, or for a
+ // non-default GwpAsan mode, or any other non-default setting, then we don't
+ // care about it.
+ procInfo = null;
}
}
processInfo = procInfo;
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 0edbea0dbd28..3efd8ad0581b 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -1320,15 +1320,26 @@ public final class GameManagerService extends IGameManagerService.Stub {
// Make sure after resetting the game mode is still supported.
// If not, set the game mode to standard
int gameMode = getGameMode(packageName, userId);
- int newGameMode = gameMode;
GamePackageConfiguration config = null;
synchronized (mOverrideConfigLock) {
config = mOverrideConfigs.get(packageName);
}
- synchronized (mDeviceConfigLock) {
- config = mConfigs.get(packageName);
+ if (config == null) {
+ synchronized (mDeviceConfigLock) {
+ config = mConfigs.get(packageName);
+ }
}
+ final int newGameMode = getNewGameMode(gameMode, config);
+ if (gameMode != newGameMode) {
+ setGameMode(packageName, GameManager.GAME_MODE_STANDARD, userId);
+ return;
+ }
+ setGameMode(packageName, gameMode, userId);
+ }
+
+ private int getNewGameMode(int gameMode, GamePackageConfiguration config) {
+ int newGameMode = gameMode;
if (config != null) {
int modesBitfield = config.getAvailableGameModesBitfield();
// Remove UNSUPPORTED to simplify the logic here, since we really just
@@ -1350,11 +1361,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
// UNSUPPORTED, then set to UNSUPPORTED
newGameMode = GameManager.GAME_MODE_UNSUPPORTED;
}
- if (gameMode != newGameMode) {
- setGameMode(packageName, GameManager.GAME_MODE_STANDARD, userId);
- return;
- }
- setGameMode(packageName, gameMode, userId);
+ return newGameMode;
}
/**
@@ -1412,7 +1419,6 @@ public final class GameManagerService extends IGameManagerService.Stub {
}
for (final String packageName : packageNames) {
int gameMode = getGameMode(packageName, userId);
- int newGameMode = gameMode;
// Make sure the user settings and package configs don't conflict.
// I.e. the user setting is set to a mode that no longer available due to
// config/manifest changes.
@@ -1421,27 +1427,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
synchronized (mDeviceConfigLock) {
config = mConfigs.get(packageName);
}
- if (config != null) {
- int modesBitfield = config.getAvailableGameModesBitfield();
- // Remove UNSUPPORTED to simplify the logic here, since we really just
- // want to check if we support selectable game modes
- modesBitfield &= ~modeToBitmask(GameManager.GAME_MODE_UNSUPPORTED);
- if (!bitFieldContainsModeBitmask(modesBitfield, gameMode)) {
- if (bitFieldContainsModeBitmask(modesBitfield,
- GameManager.GAME_MODE_STANDARD)) {
- // If the current set mode isn't supported,
- // but we support STANDARD, then set the mode to STANDARD.
- newGameMode = GameManager.GAME_MODE_STANDARD;
- } else {
- // If we don't support any game modes, then set to UNSUPPORTED
- newGameMode = GameManager.GAME_MODE_UNSUPPORTED;
- }
- }
- } else if (gameMode != GameManager.GAME_MODE_UNSUPPORTED) {
- // If we have no config for the package, but the configured mode is not
- // UNSUPPORTED, then set to UNSUPPORTED
- newGameMode = GameManager.GAME_MODE_UNSUPPORTED;
- }
+ final int newGameMode = getNewGameMode(gameMode, config);
if (newGameMode != gameMode) {
setGameMode(packageName, newGameMode, userId);
}
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 79705a32c264..bf69284df2f6 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -20,12 +20,8 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
-import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT;
+import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
-import static com.android.server.biometrics.BiometricServiceStateProto.MULTI_SENSOR_STATE_FACE_SCANNING;
-import static com.android.server.biometrics.BiometricServiceStateProto.MULTI_SENSOR_STATE_FP_SCANNING;
-import static com.android.server.biometrics.BiometricServiceStateProto.MULTI_SENSOR_STATE_SWITCHING;
-import static com.android.server.biometrics.BiometricServiceStateProto.MULTI_SENSOR_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;
@@ -100,14 +96,6 @@ public final class AuthSession implements IBinder.DeathRecipient {
@Retention(RetentionPolicy.SOURCE)
@interface SessionState {}
- /** Defined in biometrics.proto */
- @IntDef({
- MULTI_SENSOR_STATE_UNKNOWN,
- MULTI_SENSOR_STATE_FACE_SCANNING,
- MULTI_SENSOR_STATE_FP_SCANNING})
- @Retention(RetentionPolicy.SOURCE)
- @interface MultiSensorState {}
-
/**
* Notify the holder of the AuthSession that the caller/client's binder has died. The
* holder (BiometricService) should schedule {@link AuthSession#onClientDied()} to be run
@@ -119,7 +107,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
private final Context mContext;
private final IStatusBarService mStatusBarService;
- private final IBiometricSysuiReceiver mSysuiReceiver;
+ @VisibleForTesting final IBiometricSysuiReceiver mSysuiReceiver;
private final KeyStore mKeyStore;
private final Random mRandom;
private final ClientDeathReceiver mClientDeathReceiver;
@@ -133,7 +121,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
private final long mRequestId;
private final long mOperationId;
private final int mUserId;
- private final IBiometricSensorReceiver mSensorReceiver;
+ @VisibleForTesting final IBiometricSensorReceiver mSensorReceiver;
// Original receiver from BiometricPrompt.
private final IBiometricServiceReceiver mClientReceiver;
private final String mOpPackageName;
@@ -143,10 +131,10 @@ public final class AuthSession implements IBinder.DeathRecipient {
// The current state, which can be either idle, called, or started
private @SessionState int mState = STATE_AUTH_IDLE;
private @BiometricMultiSensorMode int mMultiSensorMode;
- private @MultiSensorState int mMultiSensorState;
private int[] mSensors;
// TODO(b/197265902): merge into state
private boolean mCancelled;
+ private int mAuthenticatedSensorId = -1;
// For explicit confirmation, do not send to keystore until the user has confirmed
// the authentication.
private byte[] mTokenEscrow;
@@ -232,8 +220,16 @@ public final class AuthSession implements IBinder.DeathRecipient {
}
}
- private void setSensorsToStateWaitingForCookie() throws RemoteException {
+ private void setSensorsToStateWaitingForCookie(boolean isTryAgain) throws RemoteException {
for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
+ @BiometricSensor.SensorState final int state = sensor.getSensorState();
+ if (isTryAgain
+ && state != BiometricSensor.STATE_STOPPED
+ && state != BiometricSensor.STATE_CANCELING) {
+ Slog.d(TAG, "Skip retry because sensor: " + sensor.id + " is: " + state);
+ continue;
+ }
+
final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
final boolean requireConfirmation = isConfirmationRequired(sensor);
@@ -254,7 +250,6 @@ public final class AuthSession implements IBinder.DeathRecipient {
mState = STATE_SHOWING_DEVICE_CREDENTIAL;
mSensors = new int[0];
mMultiSensorMode = BIOMETRIC_MULTI_SENSOR_DEFAULT;
- mMultiSensorState = MULTI_SENSOR_STATE_UNKNOWN;
mStatusBarService.showAuthenticationDialog(
mPromptInfo,
@@ -269,7 +264,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
mMultiSensorMode);
} else if (!mPreAuthInfo.eligibleSensors.isEmpty()) {
// Some combination of biometric or biometric|credential is requested
- setSensorsToStateWaitingForCookie();
+ setSensorsToStateWaitingForCookie(false /* isTryAgain */);
mState = STATE_AUTH_CALLED;
} else {
// No authenticators requested. This should never happen - an exception should have
@@ -283,6 +278,10 @@ public final class AuthSession implements IBinder.DeathRecipient {
Slog.w(TAG, "Received cookie but already cancelled (ignoring): " + cookie);
return;
}
+ if (hasAuthenticated()) {
+ Slog.d(TAG, "onCookieReceived after successful auth");
+ return;
+ }
for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
sensor.goToStateCookieReturnedIfCookieMatches(cookie);
@@ -307,7 +306,6 @@ public final class AuthSession implements IBinder.DeathRecipient {
}
mMultiSensorMode = getMultiSensorModeForNewSession(
mPreAuthInfo.eligibleSensors);
- mMultiSensorState = MULTI_SENSOR_STATE_UNKNOWN;
mStatusBarService.showAuthenticationDialog(mPromptInfo,
mSysuiReceiver,
@@ -381,9 +379,8 @@ public final class AuthSession implements IBinder.DeathRecipient {
// sending the final error callback to the application.
for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
try {
- final boolean shouldCancel = filter.apply(sensor);
- Slog.d(TAG, "sensorId: " + sensor.id + ", shouldCancel: " + shouldCancel);
- if (shouldCancel) {
+ if (filter.apply(sensor)) {
+ Slog.d(TAG, "Cancelling sensorId: " + sensor.id);
sensor.goToStateCancelling(mToken, mOpPackageName, mRequestId);
}
} catch (RemoteException e) {
@@ -412,10 +409,16 @@ public final class AuthSession implements IBinder.DeathRecipient {
}
}
+ // do not propagate the error and let onAuthenticationSucceeded handle the new state
+ if (hasAuthenticated()) {
+ Slog.d(TAG, "onErrorReceived after successful auth (ignoring)");
+ return false;
+ }
+
mErrorEscrow = error;
mVendorCodeEscrow = vendorCode;
- final @BiometricAuthenticator.Modality int modality = sensorIdToModality(sensorId);
+ @Modality final int modality = sensorIdToModality(sensorId);
switch (mState) {
case STATE_AUTH_CALLED: {
@@ -430,7 +433,6 @@ public final class AuthSession implements IBinder.DeathRecipient {
mState = STATE_SHOWING_DEVICE_CREDENTIAL;
mMultiSensorMode = BIOMETRIC_MULTI_SENSOR_DEFAULT;
- mMultiSensorState = MULTI_SENSOR_STATE_UNKNOWN;
mSensors = new int[0];
mStatusBarService.showAuthenticationDialog(
@@ -468,12 +470,6 @@ public final class AuthSession implements IBinder.DeathRecipient {
return true;
} else {
mState = STATE_ERROR_PENDING_SYSUI;
- if (mMultiSensorMode == BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT
- && mMultiSensorState == MULTI_SENSOR_STATE_FACE_SCANNING) {
- // wait for the UI to signal when modality should switch
- Slog.d(TAG, "onErrorReceived: waiting for modality switch callback");
- mMultiSensorState = MULTI_SENSOR_STATE_SWITCHING;
- }
mStatusBarService.onBiometricError(modality, error, vendorCode);
}
break;
@@ -505,6 +501,11 @@ public final class AuthSession implements IBinder.DeathRecipient {
}
void onAcquired(int sensorId, int acquiredInfo, int vendorCode) {
+ if (hasAuthenticated()) {
+ Slog.d(TAG, "onAcquired after successful auth");
+ return;
+ }
+
final String message = getAcquiredMessageForSensor(sensorId, acquiredInfo, vendorCode);
Slog.d(TAG, "sensorId: " + sensorId + " acquiredInfo: " + acquiredInfo
+ " message: " + message);
@@ -520,6 +521,10 @@ public final class AuthSession implements IBinder.DeathRecipient {
}
void onSystemEvent(int event) {
+ if (hasAuthenticated()) {
+ Slog.d(TAG, "onSystemEvent after successful auth");
+ return;
+ }
if (!mPromptInfo.isReceiveSystemEvents()) {
return;
}
@@ -538,53 +543,35 @@ public final class AuthSession implements IBinder.DeathRecipient {
mState = STATE_AUTH_STARTED_UI_SHOWING;
- if (mMultiSensorMode == BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT) {
- mMultiSensorState = MULTI_SENSOR_STATE_FACE_SCANNING;
- } else {
- startFingerprintSensorsNow();
- }
- }
-
- // call anytime after onDialogAnimatedIn() to indicate it's appropriate to start the
- // fingerprint sensor (i.e. face auth has failed or is not available)
- void onStartFingerprint() {
- if (mMultiSensorMode != BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT) {
- Slog.e(TAG, "onStartFingerprint, unexpected mode: " + mMultiSensorMode);
- return;
- }
-
- if (mState != STATE_AUTH_STARTED
- && mState != STATE_AUTH_STARTED_UI_SHOWING
- && mState != STATE_AUTH_PAUSED
- && mState != STATE_ERROR_PENDING_SYSUI) {
- Slog.w(TAG, "onStartFingerprint, started from unexpected state: " + mState);
- }
-
- mMultiSensorState = MULTI_SENSOR_STATE_FP_SCANNING;
- startFingerprintSensorsNow();
- }
-
- // unguarded helper for the above methods only
- private void startFingerprintSensorsNow() {
startAllPreparedFingerprintSensors();
mState = STATE_AUTH_STARTED_UI_SHOWING;
}
void onTryAgainPressed() {
+ if (hasAuthenticated()) {
+ Slog.d(TAG, "onTryAgainPressed after successful auth");
+ return;
+ }
+
if (mState != STATE_AUTH_PAUSED) {
Slog.w(TAG, "onTryAgainPressed, state: " + mState);
}
try {
- setSensorsToStateWaitingForCookie();
+ setSensorsToStateWaitingForCookie(true /* isTryAgain */);
mState = STATE_AUTH_PAUSED_RESUMING;
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException: " + e);
}
}
- void onAuthenticationSucceeded(int sensorId, boolean strong,
- byte[] token) {
+ void onAuthenticationSucceeded(int sensorId, boolean strong, byte[] token) {
+ if (hasAuthenticated()) {
+ Slog.d(TAG, "onAuthenticationSucceeded after successful auth");
+ return;
+ }
+
+ mAuthenticatedSensorId = sensorId;
if (strong) {
mTokenEscrow = token;
} else {
@@ -596,7 +583,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
try {
// Notify SysUI that the biometric has been authenticated. SysUI already knows
// the implicit/explicit state and will react accordingly.
- mStatusBarService.onBiometricAuthenticated();
+ mStatusBarService.onBiometricAuthenticated(sensorIdToModality(sensorId));
final boolean requireConfirmation = isConfirmationRequiredByAnyEligibleSensor();
@@ -609,20 +596,22 @@ public final class AuthSession implements IBinder.DeathRecipient {
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException", e);
}
+
+ cancelAllSensors(sensor -> sensor.id != sensorId);
}
- void onAuthenticationRejected() {
+ void onAuthenticationRejected(int sensorId) {
+ if (hasAuthenticated()) {
+ Slog.d(TAG, "onAuthenticationRejected after successful auth");
+ return;
+ }
+
try {
- mStatusBarService.onBiometricError(TYPE_NONE,
+ mStatusBarService.onBiometricError(sensorIdToModality(sensorId),
BiometricConstants.BIOMETRIC_PAUSED_REJECTED, 0 /* vendorCode */);
-
- // TODO: This logic will need to be updated if BP is multi-modal
- if (hasPausableBiometric()) {
- // Pause authentication. onBiometricAuthenticated(false) causes the
- // dialog to show a "try again" button for passive modalities.
+ if (pauseSensorIfSupported(sensorId)) {
mState = STATE_AUTH_PAUSED;
}
-
mClientReceiver.onAuthenticationFailed();
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException", e);
@@ -630,15 +619,34 @@ public final class AuthSession implements IBinder.DeathRecipient {
}
void onAuthenticationTimedOut(int sensorId, int cookie, int error, int vendorCode) {
+ if (hasAuthenticated()) {
+ Slog.d(TAG, "onAuthenticationTimedOut after successful auth");
+ return;
+ }
+
try {
mStatusBarService.onBiometricError(sensorIdToModality(sensorId), error, vendorCode);
+ pauseSensorIfSupported(sensorId);
mState = STATE_AUTH_PAUSED;
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException", e);
}
}
+ private boolean pauseSensorIfSupported(int sensorId) {
+ if (sensorIdToModality(sensorId) == TYPE_FACE) {
+ cancelAllSensors(sensor -> sensor.id == sensorId);
+ return true;
+ }
+ return false;
+ }
+
void onDeviceCredentialPressed() {
+ if (hasAuthenticated()) {
+ Slog.d(TAG, "onDeviceCredentialPressed after successful auth");
+ return;
+ }
+
// Cancel authentication. Skip the token/package check since we are cancelling
// from system server. The interface is permission protected so this is fine.
cancelAllSensors();
@@ -666,6 +674,10 @@ public final class AuthSession implements IBinder.DeathRecipient {
}
}
+ private boolean hasAuthenticated() {
+ return mAuthenticatedSensorId != -1;
+ }
+
private void logOnDialogDismissed(@BiometricPrompt.DismissedReason int reason) {
if (reason == BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED) {
// Explicit auth, authentication confirmed.
@@ -794,6 +806,11 @@ public final class AuthSession implements IBinder.DeathRecipient {
* @return true if this AuthSession is finished, e.g. should be set to null
*/
boolean onCancelAuthSession(boolean force) {
+ if (hasAuthenticated()) {
+ Slog.d(TAG, "onCancelAuthSession after successful auth");
+ return true;
+ }
+
mCancelled = true;
final boolean authStarted = mState == STATE_AUTH_CALLED
@@ -848,15 +865,6 @@ public final class AuthSession implements IBinder.DeathRecipient {
return remainingCookies == 0;
}
- private boolean hasPausableBiometric() {
- for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
- if (sensor.modality == TYPE_FACE) {
- return true;
- }
- }
- return false;
- }
-
@SessionState int getState() {
return mState;
}
@@ -919,7 +927,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
}
if (hasFace && hasFingerprint) {
- return BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT;
+ return BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
}
return BIOMETRIC_MULTI_SENSOR_DEFAULT;
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensor.java b/services/core/java/com/android/server/biometrics/BiometricSensor.java
index 0333c3e247c0..7166783f0b23 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensor.java
+++ b/services/core/java/com/android/server/biometrics/BiometricSensor.java
@@ -131,8 +131,10 @@ public abstract class BiometricSensor {
void goToStateCancelling(IBinder token, String opPackageName, long requestId)
throws RemoteException {
- impl.cancelAuthenticationFromService(token, opPackageName, requestId);
- mSensorState = STATE_CANCELING;
+ if (mSensorState != STATE_CANCELING) {
+ impl.cancelAuthenticationFromService(token, opPackageName, requestId);
+ mSensorState = STATE_CANCELING;
+ }
}
void goToStoppedStateIfCookieMatches(int cookie, int error) {
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 758cf7a7d430..0d9b75481ea9 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -55,7 +55,6 @@ import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -84,6 +83,7 @@ import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
/**
* System service that arbitrates the modality for BiometricPrompt to use.
@@ -92,22 +92,6 @@ public class BiometricService extends SystemService {
static final String TAG = "BiometricService";
- private static final int MSG_ON_AUTHENTICATION_SUCCEEDED = 2;
- private static final int MSG_ON_AUTHENTICATION_REJECTED = 3;
- private static final int MSG_ON_ERROR = 4;
- private static final int MSG_ON_ACQUIRED = 5;
- private static final int MSG_ON_DISMISSED = 6;
- private static final int MSG_ON_TRY_AGAIN_PRESSED = 7;
- private static final int MSG_ON_READY_FOR_AUTHENTICATION = 8;
- private static final int MSG_AUTHENTICATE = 9;
- private static final int MSG_CANCEL_AUTHENTICATION = 10;
- private static final int MSG_ON_AUTHENTICATION_TIMED_OUT = 11;
- private static final int MSG_ON_DEVICE_CREDENTIAL_PRESSED = 12;
- private static final int MSG_ON_SYSTEM_EVENT = 13;
- private static final int MSG_CLIENT_DIED = 14;
- private static final int MSG_ON_DIALOG_ANIMATED_IN = 15;
- private static final int MSG_ON_START_FINGERPRINT_NOW = 16;
-
private final Injector mInjector;
private final DevicePolicyManager mDevicePolicyManager;
@VisibleForTesting
@@ -116,7 +100,7 @@ public class BiometricService extends SystemService {
final SettingObserver mSettingObserver;
private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks;
private final Random mRandom = new Random();
- @NonNull private final AtomicLong mRequestCounter;
+ @NonNull private final Supplier<Long> mRequestCounter;
@VisibleForTesting
IStatusBarService mStatusBarService;
@@ -128,133 +112,13 @@ public class BiometricService extends SystemService {
// Get and cache the available biometric authenticators and their associated info.
final ArrayList<BiometricSensor> mSensors = new ArrayList<>();
+ @VisibleForTesting
BiometricStrengthController mBiometricStrengthController;
// The current authentication session, null if idle/done.
@VisibleForTesting
- AuthSession mCurrentAuthSession;
-
- @VisibleForTesting
- final Handler mHandler = new Handler(Looper.getMainLooper()) {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_ON_AUTHENTICATION_SUCCEEDED: {
- SomeArgs args = (SomeArgs) msg.obj;
- handleAuthenticationSucceeded(
- args.argi1 /* sensorId */,
- (byte[]) args.arg1 /* token */);
- args.recycle();
- break;
- }
-
- case MSG_ON_AUTHENTICATION_REJECTED: {
- handleAuthenticationRejected();
- break;
- }
-
- case MSG_ON_ERROR: {
- SomeArgs args = (SomeArgs) msg.obj;
- handleOnError(
- args.argi1 /* sensorId */,
- args.argi2 /* cookie */,
- args.argi3 /* error */,
- args.argi4 /* vendorCode */);
- args.recycle();
- break;
- }
-
- case MSG_ON_ACQUIRED: {
- SomeArgs args = (SomeArgs) msg.obj;
- handleOnAcquired(
- args.argi1 /* sensorId */,
- args.argi2 /* acquiredInfo */,
- args.argi3 /* vendorCode */);
- args.recycle();
- break;
- }
-
- case MSG_ON_DISMISSED: {
- handleOnDismissed(msg.arg1, (byte[]) msg.obj);
- break;
- }
-
- case MSG_ON_TRY_AGAIN_PRESSED: {
- handleOnTryAgainPressed();
- break;
- }
-
- case MSG_ON_READY_FOR_AUTHENTICATION: {
- SomeArgs args = (SomeArgs) msg.obj;
- handleOnReadyForAuthentication(
- args.argi1 /* cookie */);
- args.recycle();
- break;
- }
-
- case MSG_AUTHENTICATE: {
- SomeArgs args = (SomeArgs) msg.obj;
- handleAuthenticate(
- (IBinder) args.arg1 /* token */,
- (long) args.arg6 /* requestId */,
- (long) args.arg2 /* operationId */,
- args.argi1 /* userid */,
- (IBiometricServiceReceiver) args.arg3 /* receiver */,
- (String) args.arg4 /* opPackageName */,
- (PromptInfo) args.arg5 /* promptInfo */);
- args.recycle();
- break;
- }
-
- case MSG_CANCEL_AUTHENTICATION: {
- SomeArgs args = (SomeArgs) msg.obj;
- handleCancelAuthentication((long) args.arg3 /* requestId */);
- args.recycle();
- break;
- }
-
- case MSG_ON_AUTHENTICATION_TIMED_OUT: {
- SomeArgs args = (SomeArgs) msg.obj;
- handleAuthenticationTimedOut(
- args.argi1 /* sensorId */,
- args.argi2 /* cookie */,
- args.argi3 /* error */,
- args.argi4 /* vendorCode */);
- args.recycle();
- break;
- }
-
- case MSG_ON_DEVICE_CREDENTIAL_PRESSED: {
- handleOnDeviceCredentialPressed();
- break;
- }
-
- case MSG_ON_SYSTEM_EVENT: {
- handleOnSystemEvent((int) msg.obj);
- break;
- }
-
- case MSG_CLIENT_DIED: {
- handleClientDied();
- break;
- }
-
- case MSG_ON_DIALOG_ANIMATED_IN: {
- handleOnDialogAnimatedIn();
- break;
- }
-
- case MSG_ON_START_FINGERPRINT_NOW: {
- handleOnStartFingerprintNow();
- break;
- }
-
- default:
- Slog.e(TAG, "Unknown message: " + msg);
- break;
- }
- }
- };
+ AuthSession mAuthSession;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
/**
* Tracks authenticatorId invalidation. For more details, see
@@ -552,93 +416,74 @@ public class BiometricService extends SystemService {
}
// Receives events from individual biometric sensors.
- @VisibleForTesting
- final IBiometricSensorReceiver mBiometricSensorReceiver = new IBiometricSensorReceiver.Stub() {
- @Override
- public void onAuthenticationSucceeded(int sensorId, byte[] token) {
- SomeArgs args = SomeArgs.obtain();
- args.argi1 = sensorId;
- args.arg1 = token;
- mHandler.obtainMessage(MSG_ON_AUTHENTICATION_SUCCEEDED, args).sendToTarget();
- }
-
- @Override
- public void onAuthenticationFailed(int sensorId) {
- Slog.v(TAG, "onAuthenticationFailed");
- mHandler.obtainMessage(MSG_ON_AUTHENTICATION_REJECTED).sendToTarget();
- }
+ private IBiometricSensorReceiver createBiometricSensorReceiver(final long requestId) {
+ return new IBiometricSensorReceiver.Stub() {
+ @Override
+ public void onAuthenticationSucceeded(int sensorId, byte[] token) {
+ mHandler.post(() -> handleAuthenticationSucceeded(requestId, sensorId, token));
+ }
- @Override
- public void onError(int sensorId, int cookie, @BiometricConstants.Errors int error,
- int vendorCode) {
- // Determine if error is hard or soft error. Certain errors (such as TIMEOUT) are
- // soft errors and we should allow the user to try authenticating again instead of
- // dismissing BiometricPrompt.
- if (error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT) {
- SomeArgs args = SomeArgs.obtain();
- args.argi1 = sensorId;
- args.argi2 = cookie;
- args.argi3 = error;
- args.argi4 = vendorCode;
- mHandler.obtainMessage(MSG_ON_AUTHENTICATION_TIMED_OUT, args).sendToTarget();
- } else {
- SomeArgs args = SomeArgs.obtain();
- args.argi1 = sensorId;
- args.argi2 = cookie;
- args.argi3 = error;
- args.argi4 = vendorCode;
- mHandler.obtainMessage(MSG_ON_ERROR, args).sendToTarget();
+ @Override
+ public void onAuthenticationFailed(int sensorId) {
+ Slog.v(TAG, "onAuthenticationFailed");
+ mHandler.post(() -> handleAuthenticationRejected(requestId, sensorId));
}
- }
- @Override
- public void onAcquired(int sensorId, int acquiredInfo, int vendorCode) {
- SomeArgs args = SomeArgs.obtain();
- args.argi1 = sensorId;
- args.argi2 = acquiredInfo;
- args.argi3 = vendorCode;
- mHandler.obtainMessage(MSG_ON_ACQUIRED, args).sendToTarget();
- }
- };
+ @Override
+ public void onError(int sensorId, int cookie, @BiometricConstants.Errors int error,
+ int vendorCode) {
+ // Determine if error is hard or soft error. Certain errors (such as TIMEOUT) are
+ // soft errors and we should allow the user to try authenticating again instead of
+ // dismissing BiometricPrompt.
+ if (error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT) {
+ mHandler.post(() -> handleAuthenticationTimedOut(
+ requestId, sensorId, cookie, error, vendorCode));
+ } else {
+ mHandler.post(() -> handleOnError(
+ requestId, sensorId, cookie, error, vendorCode));
+ }
+ }
- final IBiometricSysuiReceiver mSysuiReceiver = new IBiometricSysuiReceiver.Stub() {
- @Override
- public void onDialogDismissed(@BiometricPrompt.DismissedReason int reason,
- @Nullable byte[] credentialAttestation) {
- mHandler.obtainMessage(MSG_ON_DISMISSED,
- reason,
- 0 /* arg2 */,
- credentialAttestation /* obj */).sendToTarget();
- }
+ @Override
+ public void onAcquired(int sensorId, int acquiredInfo, int vendorCode) {
+ mHandler.post(() -> handleOnAcquired(
+ requestId, sensorId, acquiredInfo, vendorCode));
+ }
+ };
+ }
- @Override
- public void onTryAgainPressed() {
- mHandler.sendEmptyMessage(MSG_ON_TRY_AGAIN_PRESSED);
- }
+ private IBiometricSysuiReceiver createSysuiReceiver(final long requestId) {
+ return new IBiometricSysuiReceiver.Stub() {
+ @Override
+ public void onDialogDismissed(@BiometricPrompt.DismissedReason int reason,
+ @Nullable byte[] credentialAttestation) {
+ mHandler.post(() -> handleOnDismissed(requestId, reason, credentialAttestation));
+ }
- @Override
- public void onDeviceCredentialPressed() {
- mHandler.sendEmptyMessage(MSG_ON_DEVICE_CREDENTIAL_PRESSED);
- }
+ @Override
+ public void onTryAgainPressed() {
+ mHandler.post(() -> handleOnTryAgainPressed(requestId));
+ }
- @Override
- public void onSystemEvent(int event) {
- mHandler.obtainMessage(MSG_ON_SYSTEM_EVENT, event).sendToTarget();
- }
+ @Override
+ public void onDeviceCredentialPressed() {
+ mHandler.post(() -> handleOnDeviceCredentialPressed(requestId));
+ }
- @Override
- public void onDialogAnimatedIn() {
- mHandler.obtainMessage(MSG_ON_DIALOG_ANIMATED_IN).sendToTarget();
- }
+ @Override
+ public void onSystemEvent(int event) {
+ mHandler.post(() -> handleOnSystemEvent(requestId, event));
+ }
- @Override
- public void onStartFingerprintNow() {
- mHandler.obtainMessage(MSG_ON_START_FINGERPRINT_NOW).sendToTarget();
- }
- };
+ @Override
+ public void onDialogAnimatedIn() {
+ mHandler.post(() -> handleOnDialogAnimatedIn(requestId));
+ }
+ };
+ }
- private final AuthSession.ClientDeathReceiver mClientDeathReceiver = () -> {
- mHandler.sendEmptyMessage(MSG_CLIENT_DIED);
+ private AuthSession.ClientDeathReceiver createClientDeathReceiver(final long requestId) {
+ return () -> mHandler.post(() -> handleClientDied(requestId));
};
/**
@@ -679,12 +524,10 @@ public class BiometricService extends SystemService {
}
@Override // Binder call
- public void onReadyForAuthentication(int cookie) {
+ public void onReadyForAuthentication(long requestId, int cookie) {
checkInternalPermission();
- SomeArgs args = SomeArgs.obtain();
- args.argi1 = cookie;
- mHandler.obtainMessage(MSG_ON_READY_FOR_AUTHENTICATION, args).sendToTarget();
+ mHandler.post(() -> handleOnReadyForAuthentication(requestId, cookie));
}
@Override // Binder call
@@ -711,18 +554,9 @@ public class BiometricService extends SystemService {
}
}
- final long requestId = mRequestCounter.incrementAndGet();
-
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = token;
- args.arg2 = operationId;
- args.argi1 = userId;
- args.arg3 = receiver;
- args.arg4 = opPackageName;
- args.arg5 = promptInfo;
- args.arg6 = requestId;
-
- mHandler.obtainMessage(MSG_AUTHENTICATE, args).sendToTarget();
+ final long requestId = mRequestCounter.get();
+ mHandler.post(() -> handleAuthenticate(
+ token, requestId, operationId, userId, receiver, opPackageName, promptInfo));
return requestId;
}
@@ -736,7 +570,7 @@ public class BiometricService extends SystemService {
args.arg2 = opPackageName;
args.arg3 = requestId;
- mHandler.obtainMessage(MSG_CANCEL_AUTHENTICATION, args).sendToTarget();
+ mHandler.post(() -> handleCancelAuthentication(requestId));
}
@Override // Binder call
@@ -1002,8 +836,7 @@ public class BiometricService extends SystemService {
Slog.d(TAG, "ClearSchedulerBuffer: " + clearSchedulerBuffer);
final ProtoOutputStream proto = new ProtoOutputStream(fd);
proto.write(BiometricServiceStateProto.AUTH_SESSION_STATE,
- mCurrentAuthSession != null ? mCurrentAuthSession.getState()
- : STATE_AUTH_IDLE);
+ mAuthSession != null ? mAuthSession.getState() : STATE_AUTH_IDLE);
for (BiometricSensor sensor : mSensors) {
byte[] serviceState = sensor.impl
.dumpSensorServiceStateProto(clearSchedulerBuffer);
@@ -1128,8 +961,9 @@ public class BiometricService extends SystemService {
CoexCoordinator.FACE_HAPTIC_DISABLE, 1) != 0;
}
- public AtomicLong getRequestGenerator() {
- return new AtomicLong(0);
+ public Supplier<Long> getRequestGenerator() {
+ final AtomicLong generator = new AtomicLong(0);
+ return () -> generator.incrementAndGet();
}
}
@@ -1202,172 +1036,184 @@ public class BiometricService extends SystemService {
return false;
}
- private void handleAuthenticationSucceeded(int sensorId, byte[] token) {
+ @Nullable
+ private AuthSession getAuthSessionIfCurrent(long requestId) {
+ final AuthSession session = mAuthSession;
+ if (session != null && session.getRequestId() == requestId) {
+ return session;
+ }
+ return null;
+ }
+
+ private void handleAuthenticationSucceeded(long requestId, int sensorId, byte[] token) {
Slog.v(TAG, "handleAuthenticationSucceeded(), sensorId: " + sensorId);
// Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
// after user dismissed/canceled dialog).
- if (mCurrentAuthSession == null) {
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
Slog.e(TAG, "handleAuthenticationSucceeded: AuthSession is null");
return;
}
- mCurrentAuthSession.onAuthenticationSucceeded(sensorId, isStrongBiometric(sensorId), token);
+ session.onAuthenticationSucceeded(sensorId, isStrongBiometric(sensorId), token);
}
- private void handleAuthenticationRejected() {
+ private void handleAuthenticationRejected(long requestId, int sensorId) {
Slog.v(TAG, "handleAuthenticationRejected()");
// Should never happen, log this to catch bad HAL behavior (e.g. auth rejected
// after user dismissed/canceled dialog).
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "handleAuthenticationRejected: AuthSession is null");
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.w(TAG, "handleAuthenticationRejected: AuthSession is not current");
return;
}
- mCurrentAuthSession.onAuthenticationRejected();
+ session.onAuthenticationRejected(sensorId);
}
- private void handleAuthenticationTimedOut(int sensorId, int cookie, int error, int vendorCode) {
+ private void handleAuthenticationTimedOut(long requestId, int sensorId, int cookie, int error,
+ int vendorCode) {
Slog.v(TAG, "handleAuthenticationTimedOut(), sensorId: " + sensorId
+ ", cookie: " + cookie
+ ", error: " + error
+ ", vendorCode: " + vendorCode);
// Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
// after user dismissed/canceled dialog).
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "handleAuthenticationTimedOut: AuthSession is null");
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.w(TAG, "handleAuthenticationTimedOut: AuthSession is not current");
return;
}
- mCurrentAuthSession.onAuthenticationTimedOut(sensorId, cookie, error, vendorCode);
+ session.onAuthenticationTimedOut(sensorId, cookie, error, vendorCode);
}
- private void handleOnError(int sensorId, int cookie, @BiometricConstants.Errors int error,
- int vendorCode) {
+ private void handleOnError(long requestId, int sensorId, int cookie,
+ @BiometricConstants.Errors int error, int vendorCode) {
Slog.d(TAG, "handleOnError() sensorId: " + sensorId
+ ", cookie: " + cookie
+ ", error: " + error
+ ", vendorCode: " + vendorCode);
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "handleOnError: AuthSession is null");
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.w(TAG, "handleOnError: AuthSession is not current");
return;
}
try {
- final boolean finished = mCurrentAuthSession
- .onErrorReceived(sensorId, cookie, error, vendorCode);
+ final boolean finished = session.onErrorReceived(sensorId, cookie, error, vendorCode);
if (finished) {
Slog.d(TAG, "handleOnError: AuthSession finished");
- mCurrentAuthSession = null;
+ mAuthSession = null;
}
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException", e);
}
}
- private void handleOnAcquired(int sensorId, int acquiredInfo, int vendorCode) {
+ private void handleOnAcquired(long requestId, int sensorId, int acquiredInfo, int vendorCode) {
// Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
// after user dismissed/canceled dialog).
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "onAcquired: AuthSession is null");
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.w(TAG, "onAcquired: AuthSession is not current");
return;
}
- mCurrentAuthSession.onAcquired(sensorId, acquiredInfo, vendorCode);
+ session.onAcquired(sensorId, acquiredInfo, vendorCode);
}
- private void handleOnDismissed(@BiometricPrompt.DismissedReason int reason,
+ private void handleOnDismissed(long requestId, @BiometricPrompt.DismissedReason int reason,
@Nullable byte[] credentialAttestation) {
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "onDismissed: " + reason + ", AuthSession is null");
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.e(TAG, "onDismissed: " + reason + ", AuthSession is not current");
return;
}
- mCurrentAuthSession.onDialogDismissed(reason, credentialAttestation);
- mCurrentAuthSession = null;
+ session.onDialogDismissed(reason, credentialAttestation);
+ mAuthSession = null;
}
- private void handleOnTryAgainPressed() {
+ private void handleOnTryAgainPressed(long requestId) {
Slog.d(TAG, "onTryAgainPressed");
// No need to check permission, since it can only be invoked by SystemUI
// (or system server itself).
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "handleOnTryAgainPressed: AuthSession is null");
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.w(TAG, "handleOnTryAgainPressed: AuthSession is not current");
return;
}
- mCurrentAuthSession.onTryAgainPressed();
+ session.onTryAgainPressed();
}
- private void handleOnDeviceCredentialPressed() {
+ private void handleOnDeviceCredentialPressed(long requestId) {
Slog.d(TAG, "onDeviceCredentialPressed");
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "handleOnDeviceCredentialPressed: AuthSession is null");
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.w(TAG, "handleOnDeviceCredentialPressed: AuthSession is not current");
return;
}
- mCurrentAuthSession.onDeviceCredentialPressed();
+ session.onDeviceCredentialPressed();
}
- private void handleOnSystemEvent(int event) {
+ private void handleOnSystemEvent(long requestId, int event) {
Slog.d(TAG, "onSystemEvent: " + event);
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "handleOnSystemEvent: AuthSession is null");
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.w(TAG, "handleOnSystemEvent: AuthSession is not current");
return;
}
- mCurrentAuthSession.onSystemEvent(event);
+ session.onSystemEvent(event);
}
- private void handleClientDied() {
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "handleClientDied: AuthSession is null");
+ private void handleClientDied(long requestId) {
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.w(TAG, "handleClientDied: AuthSession is not current");
return;
}
- Slog.e(TAG, "Session: " + mCurrentAuthSession);
- final boolean finished = mCurrentAuthSession.onClientDied();
+ Slog.e(TAG, "Session: " + session);
+ final boolean finished = session.onClientDied();
if (finished) {
- mCurrentAuthSession = null;
+ mAuthSession = null;
}
}
- private void handleOnDialogAnimatedIn() {
+ private void handleOnDialogAnimatedIn(long requestId) {
Slog.d(TAG, "handleOnDialogAnimatedIn");
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "handleOnDialogAnimatedIn: AuthSession is null");
- return;
- }
-
- mCurrentAuthSession.onDialogAnimatedIn();
- }
- private void handleOnStartFingerprintNow() {
- Slog.d(TAG, "handleOnStartFingerprintNow");
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "handleOnStartFingerprintNow: AuthSession is null");
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.w(TAG, "handleOnDialogAnimatedIn: AuthSession is not current");
return;
}
- mCurrentAuthSession.onStartFingerprint();
+ session.onDialogAnimatedIn();
}
/**
* Invoked when each service has notified that its client is ready to be started. When
* all biometrics are ready, this invokes the SystemUI dialog through StatusBar.
*/
- private void handleOnReadyForAuthentication(int cookie) {
- if (mCurrentAuthSession == null) {
+ private void handleOnReadyForAuthentication(long requestId, int cookie) {
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
// Only should happen if a biometric was locked out when authenticate() was invoked.
// In that case, if device credentials are allowed, the UI is already showing. If not
// allowed, the error has already been returned to the caller.
- Slog.w(TAG, "handleOnReadyForAuthentication: AuthSession is null");
+ Slog.w(TAG, "handleOnReadyForAuthentication: AuthSession is not current");
return;
}
- mCurrentAuthSession.onCookieReceived(cookie);
+ session.onCookieReceived(cookie);
}
private void handleAuthenticate(IBinder token, long requestId, long operationId, int userId,
@@ -1428,47 +1274,41 @@ public class BiometricService extends SystemService {
// No need to dismiss dialog / send error yet if we're continuing authentication, e.g.
// "Try again" is showing due to something like ERROR_TIMEOUT.
- if (mCurrentAuthSession != null) {
+ if (mAuthSession != null) {
// Forcefully cancel authentication. Dismiss the UI, and immediately send
// ERROR_CANCELED to the client. Note that we should/will ignore HAL ERROR_CANCELED.
// Expect to see some harmless "unknown cookie" errors.
- Slog.w(TAG, "Existing AuthSession: " + mCurrentAuthSession);
- mCurrentAuthSession.onCancelAuthSession(true /* force */);
- mCurrentAuthSession = null;
+ Slog.w(TAG, "Existing AuthSession: " + mAuthSession);
+ mAuthSession.onCancelAuthSession(true /* force */);
+ mAuthSession = null;
}
final boolean debugEnabled = mInjector.isDebugEnabled(getContext(), userId);
- mCurrentAuthSession = new AuthSession(getContext(), mStatusBarService, mSysuiReceiver,
- mKeyStore, mRandom, mClientDeathReceiver, preAuthInfo, token, requestId,
- operationId, userId, mBiometricSensorReceiver, receiver, opPackageName, promptInfo,
- debugEnabled, mInjector.getFingerprintSensorProperties(getContext()));
+ mAuthSession = new AuthSession(getContext(), mStatusBarService,
+ createSysuiReceiver(requestId), mKeyStore, mRandom,
+ createClientDeathReceiver(requestId), preAuthInfo, token, requestId,
+ operationId, userId, createBiometricSensorReceiver(requestId), receiver,
+ opPackageName, promptInfo, debugEnabled,
+ mInjector.getFingerprintSensorProperties(getContext()));
try {
- mCurrentAuthSession.goToInitialState();
+ mAuthSession.goToInitialState();
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException", e);
}
}
private void handleCancelAuthentication(long requestId) {
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "handleCancelAuthentication: AuthSession is null");
- return;
- }
- if (mCurrentAuthSession.getRequestId() != requestId) {
- // TODO: actually cancel the operation
- // This can happen if the operation has been queued, but is cancelled before
- // it reaches the head of the scheduler. Consider it a programming error for now
- // and ignore it.
- Slog.e(TAG, "handleCancelAuthentication: AuthSession mismatch current requestId: "
- + mCurrentAuthSession.getRequestId() + " cancel for: " + requestId
- + " (ignoring cancellation)");
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.w(TAG, "handleCancelAuthentication: AuthSession is not current");
+ // TODO: actually cancel the operation?
return;
}
- final boolean finished = mCurrentAuthSession.onCancelAuthSession(false /* force */);
+ final boolean finished = session.onCancelAuthSession(false /* force */);
if (finished) {
Slog.d(TAG, "handleCancelAuthentication: AuthSession finished");
- mCurrentAuthSession = null;
+ mAuthSession = null;
}
}
@@ -1491,7 +1331,7 @@ public class BiometricService extends SystemService {
pw.println(" " + sensor);
}
pw.println();
- pw.println("CurrentSession: " + mCurrentAuthSession);
+ pw.println("CurrentSession: " + mAuthSession);
pw.println();
pw.println("CoexCoordinator: " + CoexCoordinator.getInstance().toString());
pw.println();
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 54b79e1f8e4a..6d687726dbe8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -86,6 +86,7 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
private long mStartTimeMs;
private boolean mAuthAttempted;
+ private boolean mAuthSuccess = false;
// TODO: This is currently hard to maintain, as each AuthenticationClient subclass must update
// the state. We should think of a way to improve this in the future.
@@ -237,6 +238,7 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
"Successful background authentication!");
}
+ mAuthSuccess = true;
markAlreadyDone();
if (mTaskStackListener != null) {
@@ -502,6 +504,11 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
return mAuthAttempted;
}
+ /** If an auth attempt completed successfully. */
+ public boolean wasAuthSuccessful() {
+ return mAuthSuccess;
+ }
+
protected int getShowOverlayReason() {
if (isKeyguard()) {
return BiometricOverlayConstants.REASON_AUTH_KEYGUARD;
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 1a6da94f683e..d0ec4470d3e6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -316,7 +316,8 @@ public class BiometricScheduler {
}
} else {
try {
- mBiometricService.onReadyForAuthentication(cookie);
+ mBiometricService.onReadyForAuthentication(
+ mCurrentOperation.getClientMonitor().getRequestId(), cookie);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when contacting BiometricService", e);
}
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 f1c786b4977c..46d863d7aaec 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
@@ -123,7 +123,8 @@ public class ClientMonitorCallbackConverter {
}
}
- void onRemoved(BiometricAuthenticator.Identifier identifier, int remaining)
+ /** Called when a user has been removed. */
+ public void onRemoved(BiometricAuthenticator.Identifier identifier, int remaining)
throws RemoteException {
if (mFaceServiceReceiver != null) {
mFaceServiceReceiver.onRemoved((Face) identifier, remaining);
diff --git a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
index 25d4a38cd475..5aa9b79c074c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
@@ -173,18 +173,13 @@ public class CoexCoordinator {
}
// SensorType to AuthenticationClient map
- private final Map<Integer, AuthenticationClient<?>> mClientMap;
- @VisibleForTesting final LinkedList<SuccessfulAuth> mSuccessfulAuths;
+ private final Map<Integer, AuthenticationClient<?>> mClientMap = new HashMap<>();
+ @VisibleForTesting final LinkedList<SuccessfulAuth> mSuccessfulAuths = new LinkedList<>();
private boolean mAdvancedLogicEnabled;
private boolean mFaceHapticDisabledWhenNonBypass;
- private final Handler mHandler;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
- private CoexCoordinator() {
- // Singleton
- mClientMap = new HashMap<>();
- mSuccessfulAuths = new LinkedList<>();
- mHandler = new Handler(Looper.getMainLooper());
- }
+ private CoexCoordinator() {}
public void addAuthenticationClient(@BiometricScheduler.SensorType int sensorType,
@NonNull AuthenticationClient<?> client) {
@@ -221,8 +216,14 @@ public class CoexCoordinator {
public void onAuthenticationSucceeded(long currentTimeMillis,
@NonNull AuthenticationClient<?> client,
@NonNull Callback callback) {
+ final boolean isUsingSingleModality = isSingleAuthOnly(client);
+
if (client.isBiometricPrompt()) {
- callback.sendHapticFeedback();
+ if (!isUsingSingleModality && hasMultipleSuccessfulAuthentications()) {
+ // only send feedback on the first one
+ } else {
+ callback.sendHapticFeedback();
+ }
// For BP, BiometricService will add the authToken to Keystore.
callback.sendAuthenticationResult(false /* addAuthTokenIfStrong */);
callback.handleLifecycleAfterAuth();
@@ -234,7 +235,7 @@ public class CoexCoordinator {
callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
callback.handleLifecycleAfterAuth();
} else if (mAdvancedLogicEnabled && client.isKeyguard()) {
- if (isSingleAuthOnly(client)) {
+ if (isUsingSingleModality) {
// Single sensor authentication
callback.sendHapticFeedback();
callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
@@ -295,10 +296,10 @@ public class CoexCoordinator {
@NonNull AuthenticationClient<?> client,
@LockoutTracker.LockoutMode int lockoutMode,
@NonNull Callback callback) {
- final boolean keyguardAdvancedLogic = mAdvancedLogicEnabled && client.isKeyguard();
+ final boolean isUsingSingleModality = isSingleAuthOnly(client);
- if (keyguardAdvancedLogic) {
- if (isSingleAuthOnly(client)) {
+ if (mAdvancedLogicEnabled && client.isKeyguard()) {
+ if (isUsingSingleModality) {
callback.sendHapticFeedback();
callback.handleLifecycleAfterAuth();
} else {
@@ -319,8 +320,7 @@ public class CoexCoordinator {
// also done now.
callback.sendHapticFeedback();
callback.handleLifecycleAfterAuth();
- }
- else {
+ } else {
// UDFPS auth has never been attempted.
if (mFaceHapticDisabledWhenNonBypass && !face.isKeyguardBypassEnabled()) {
Slog.w(TAG, "Skipping face reject haptic");
@@ -360,6 +360,11 @@ public class CoexCoordinator {
callback.handleLifecycleAfterAuth();
}
}
+ } else if (client.isBiometricPrompt() && !isUsingSingleModality) {
+ if (!isCurrentFaceAuth(client)) {
+ callback.sendHapticFeedback();
+ }
+ callback.handleLifecycleAfterAuth();
} else {
callback.sendHapticFeedback();
callback.handleLifecycleAfterAuth();
@@ -380,6 +385,8 @@ public class CoexCoordinator {
*/
public void onAuthenticationError(@NonNull AuthenticationClient<?> client,
@BiometricConstants.Errors int error, @NonNull ErrorCallback callback) {
+ final boolean isUsingSingleModality = isSingleAuthOnly(client);
+
// Figure out non-coex state
final boolean shouldUsuallyVibrate;
if (isCurrentFaceAuth(client)) {
@@ -401,25 +408,26 @@ public class CoexCoordinator {
}
// Figure out coex state
- final boolean keyguardAdvancedLogic = mAdvancedLogicEnabled && client.isKeyguard();
final boolean hapticSuppressedByCoex;
-
- if (keyguardAdvancedLogic) {
- if (isSingleAuthOnly(client)) {
+ if (mAdvancedLogicEnabled && client.isKeyguard()) {
+ if (isUsingSingleModality) {
hapticSuppressedByCoex = false;
} else {
hapticSuppressedByCoex = isCurrentFaceAuth(client)
&& !client.isKeyguardBypassEnabled();
}
+ } else if (client.isBiometricPrompt() && !isUsingSingleModality) {
+ hapticSuppressedByCoex = isCurrentFaceAuth(client);
} else {
hapticSuppressedByCoex = false;
}
// Combine and send feedback if appropriate
- Slog.d(TAG, "shouldUsuallyVibrate: " + shouldUsuallyVibrate
- + ", hapticSuppressedByCoex: " + hapticSuppressedByCoex);
if (shouldUsuallyVibrate && !hapticSuppressedByCoex) {
callback.sendHapticFeedback();
+ } else {
+ Slog.v(TAG, "no haptic shouldUsuallyVibrate: " + shouldUsuallyVibrate
+ + ", hapticSuppressedByCoex: " + hapticSuppressedByCoex);
}
}
@@ -504,6 +512,19 @@ public class CoexCoordinator {
return true;
}
+ private boolean hasMultipleSuccessfulAuthentications() {
+ int count = 0;
+ for (AuthenticationClient<?> c : mClientMap.values()) {
+ if (c.wasAuthSuccessful()) {
+ count++;
+ }
+ if (count > 1) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index 07ce841a7cac..e0d519469e32 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -19,6 +19,7 @@ package com.android.server.biometrics.sensors;
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricConstants;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
@@ -71,6 +72,24 @@ public abstract class RemovalClient<S extends BiometricAuthenticator.Identifier,
@Override
public void onRemoved(@NonNull BiometricAuthenticator.Identifier identifier, int remaining) {
+ // This happens when we have failed to remove a biometric.
+ if (identifier == null) {
+ Slog.e(TAG, "identifier was null, skipping onRemove()");
+ try {
+ if (getListener() != null) {
+ getListener().onError(getSensorId(), getCookie(),
+ BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_REMOVE,
+ 0 /* vendorCode */);
+ } else {
+ Slog.e(TAG, "Error, listener was null, not sending onError callback");
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to send error to client for onRemoved", e);
+ }
+ mCallback.onClientFinished(this, false /* success */);
+ return;
+ }
+
Slog.d(TAG, "onRemoved: " + identifier.getBiometricId() + " remaining: " + remaining);
mBiometricUtils.removeBiometricForUser(getContext(), getTargetUserId(),
identifier.getBiometricId());
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 9bfdd687644b..66e9da040102 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2579,7 +2579,7 @@ public final class DisplayManagerService extends SystemService {
boolean getAllowNonNativeRefreshRateOverride() {
return DisplayProperties
- .debug_allow_non_native_refresh_rate_override().orElse(false);
+ .debug_allow_non_native_refresh_rate_override().orElse(true);
}
@NonNull
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
index a102406cc131..1203769cb72b 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
@@ -262,7 +262,7 @@ public class LockSettingsStrongAuth {
long nextAlarmTime = strongAuthTime + dpm.getRequiredStrongAuthTimeout(null, userId);
// schedule a new alarm listener for the user
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextAlarmTime,
+ mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextAlarmTime,
STRONG_AUTH_TIMEOUT_ALARM_TAG, alarm, mHandler);
}
@@ -303,7 +303,7 @@ public class LockSettingsStrongAuth {
alarm = new NonStrongBiometricTimeoutAlarmListener(userId);
mNonStrongBiometricTimeoutAlarmListener.put(userId, alarm);
// schedule a new alarm listener for the user
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextAlarmTime,
+ mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextAlarmTime,
NON_STRONG_BIOMETRIC_TIMEOUT_ALARM_TAG, alarm, mHandler);
}
@@ -394,7 +394,7 @@ public class LockSettingsStrongAuth {
}
// schedule a new alarm listener for the user
if (DEBUG) Slog.d(TAG, "Schedule a new alarm for non-strong biometric idle timeout");
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextAlarmTime,
+ mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextAlarmTime,
NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_ALARM_TAG, alarm, mHandler);
}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index ca3ee8558dd0..0c9855b2385d 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2812,8 +2812,8 @@ public class ComputerEngine implements Computer {
"MATCH_ANY_USER flag requires INTERACT_ACROSS_USERS permission");
} else if ((flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0
&& isCallerSystemUser
- && mUserManager.hasManagedProfile(UserHandle.USER_SYSTEM)) {
- // If the caller wants all packages and has a restricted profile associated with it,
+ && mUserManager.hasProfile(UserHandle.USER_SYSTEM)) {
+ // If the caller wants all packages and has a profile associated with it,
// then match all users. This is to make sure that launchers that need to access
//work
// profile apps don't start breaking. TODO: Remove this hack when launchers stop
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 54c201945be6..ceab92577bb3 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2893,9 +2893,13 @@ final class InstallPackageHelper {
}
}
- final boolean deferInstallObserver = succeeded && update && !killApp;
+ final boolean deferInstallObserver = succeeded && update;
if (deferInstallObserver) {
- mPm.scheduleDeferredNoKillInstallObserver(res, installObserver);
+ if (killApp) {
+ mPm.scheduleDeferredPendingKillInstallObserver(res, installObserver);
+ } else {
+ mPm.scheduleDeferredNoKillInstallObserver(res, installObserver);
+ }
} else {
mPm.notifyInstallObserver(res, installObserver);
}
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index b028a2cef2a5..e8faca9765f8 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -24,6 +24,7 @@ import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
import static com.android.server.pm.PackageManagerService.DEFAULT_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD;
import static com.android.server.pm.PackageManagerService.DEFERRED_NO_KILL_INSTALL_OBSERVER;
import static com.android.server.pm.PackageManagerService.DEFERRED_NO_KILL_POST_DELETE;
+import static com.android.server.pm.PackageManagerService.DEFERRED_PENDING_KILL_INSTALL_OBSERVER;
import static com.android.server.pm.PackageManagerService.DOMAIN_VERIFICATION;
import static com.android.server.pm.PackageManagerService.ENABLE_ROLLBACK_STATUS;
import static com.android.server.pm.PackageManagerService.ENABLE_ROLLBACK_TIMEOUT;
@@ -126,10 +127,12 @@ final class PackageHandler extends Handler {
}
}
} break;
- case DEFERRED_NO_KILL_INSTALL_OBSERVER: {
- String packageName = (String) msg.obj;
+ case DEFERRED_NO_KILL_INSTALL_OBSERVER:
+ case DEFERRED_PENDING_KILL_INSTALL_OBSERVER: {
+ final String packageName = (String) msg.obj;
if (packageName != null) {
- mPm.notifyInstallObserver(packageName);
+ final boolean killApp = msg.what == DEFERRED_PENDING_KILL_INSTALL_OBSERVER;
+ mPm.notifyInstallObserver(packageName, killApp);
}
} break;
case WRITE_SETTINGS: {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 0602f3e72031..6fbad24d58c0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -840,6 +840,9 @@ public class PackageManagerService extends IPackageManager.Stub
private final Map<String, Pair<PackageInstalledInfo, IPackageInstallObserver2>>
mNoKillInstallObservers = Collections.synchronizedMap(new HashMap<>());
+ private final Map<String, Pair<PackageInstalledInfo, IPackageInstallObserver2>>
+ mPendingKillInstallObservers = Collections.synchronizedMap(new HashMap<>());
+
// Internal interface for permission manager
final PermissionManagerServiceInternal mPermissionManager;
@@ -887,9 +890,11 @@ public class PackageManagerService extends IPackageManager.Stub
static final int CHECK_PENDING_INTEGRITY_VERIFICATION = 26;
static final int DOMAIN_VERIFICATION = 27;
static final int PRUNE_UNUSED_STATIC_SHARED_LIBRARIES = 28;
+ static final int DEFERRED_PENDING_KILL_INSTALL_OBSERVER = 29;
static final int DEFERRED_NO_KILL_POST_DELETE_DELAY_MS = 3 * 1000;
private static final int DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS = 500;
+ private static final int DEFERRED_PENDING_KILL_INSTALL_OBSERVER_DELAY_MS = 1000;
static final int WRITE_SETTINGS_DELAY = 10*1000; // 10 seconds
@@ -1166,13 +1171,14 @@ public class PackageManagerService extends IPackageManager.Stub
Computer computer = snapshotComputer();
ArraySet<String> packagesToNotify = computer.getNotifyPackagesForReplacedReceived(packages);
for (int index = 0; index < packagesToNotify.size(); index++) {
- notifyInstallObserver(packagesToNotify.valueAt(index));
+ notifyInstallObserver(packagesToNotify.valueAt(index), false /* killApp */);
}
}
- void notifyInstallObserver(String packageName) {
- Pair<PackageInstalledInfo, IPackageInstallObserver2> pair =
- mNoKillInstallObservers.remove(packageName);
+ void notifyInstallObserver(String packageName, boolean killApp) {
+ final Pair<PackageInstalledInfo, IPackageInstallObserver2> pair =
+ killApp ? mPendingKillInstallObservers.remove(packageName)
+ : mNoKillInstallObservers.remove(packageName);
if (pair != null) {
notifyInstallObserver(pair.first, pair.second);
@@ -1211,6 +1217,15 @@ public class PackageManagerService extends IPackageManager.Stub
delay ? getPruneUnusedSharedLibrariesDelay() : 0);
}
+ void scheduleDeferredPendingKillInstallObserver(PackageInstalledInfo info,
+ IPackageInstallObserver2 observer) {
+ final String packageName = info.mPkg.getPackageName();
+ mPendingKillInstallObservers.put(packageName, Pair.create(info, observer));
+ final Message message = mHandler.obtainMessage(DEFERRED_PENDING_KILL_INSTALL_OBSERVER,
+ packageName);
+ mHandler.sendMessageDelayed(message, DEFERRED_PENDING_KILL_INSTALL_OBSERVER_DELAY_MS);
+ }
+
private static long getPruneUnusedSharedLibrariesDelay() {
return SystemProperties.getLong("debug.pm.prune_unused_shared_libraries_delay",
PRUNE_UNUSED_SHARED_LIBRARIES_DELAY);
@@ -2055,8 +2070,6 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- mPrepareAppDataFuture = mAppDataHelper.fixAppsDataOnBoot();
-
// If this is first boot after an OTA, and a normal boot, then
// we need to clear code cache directories.
// Note that we do *not* clear the application profiles. These remain valid
@@ -2077,6 +2090,9 @@ public class PackageManagerService extends IPackageManager.Stub
ver.fingerprint = PackagePartitions.FINGERPRINT;
}
+ // Defer the app data fixup until we are done with app data clearing above.
+ mPrepareAppDataFuture = mAppDataHelper.fixAppsDataOnBoot();
+
// Legacy existing (installed before Q) non-system apps to hide
// their icons in launcher.
if (!mOnlyCore && mIsPreQUpgrade) {
@@ -2557,8 +2573,9 @@ public class PackageManagerService extends IPackageManager.Stub
try {
return super.onTransact(code, data, reply, flags);
} catch (RuntimeException e) {
- if (!(e instanceof SecurityException) && !(e instanceof IllegalArgumentException)) {
- Slog.wtf(TAG, "Package Manager Crash", e);
+ if (!(e instanceof SecurityException) && !(e instanceof IllegalArgumentException)
+ && !(e instanceof ParcelableException)) {
+ Slog.wtf(TAG, "Package Manager Unexpected Exception", e);
}
throw e;
}
@@ -6675,6 +6692,11 @@ public class PackageManagerService extends IPackageManager.Stub
@Override
public IPackageInstaller getPackageInstaller() {
+ // Return installer service for internal calls.
+ if (PackageManagerServiceUtils.isSystemOrRoot()) {
+ return mInstallerService;
+ }
+ // Return null for InstantApps.
if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
return null;
}
@@ -7393,6 +7415,12 @@ public class PackageManagerService extends IPackageManager.Stub
}
@Override
+ public void onPackageProcessKilledForUninstall(String packageName) {
+ mHandler.post(() -> PackageManagerService.this.notifyInstallObserver(packageName,
+ true /* killApp */));
+ }
+
+ @Override
public SparseArray<String> getAppsWithSharedUserIds() {
return mComputer.getAppsWithSharedUserIds();
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index a9471cf57070..8d3fbf7fc679 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -1250,6 +1250,14 @@ public class PackageManagerServiceUtils {
}
/**
+ * Check if the Binder caller is system UID or root's UID.
+ */
+ public static boolean isSystemOrRoot() {
+ final int uid = Binder.getCallingUid();
+ return uid == Process.SYSTEM_UID || uid == Process.ROOT_UID;
+ }
+
+ /**
* Enforces that only the system UID or root's UID can call a method exposed
* via Binder.
*
@@ -1257,8 +1265,7 @@ public class PackageManagerServiceUtils {
* @throws SecurityException if the caller is not system or root
*/
public static void enforceSystemOrRoot(String message) {
- final int uid = Binder.getCallingUid();
- if (uid != Process.SYSTEM_UID && uid != Process.ROOT_UID) {
+ if (!isSystemOrRoot()) {
throw new SecurityException(message);
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b7c55c57c6ce..fed214fd5ab0 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -6263,11 +6263,11 @@ public class UserManagerService extends IUserManager.Stub {
}
/**
- * Checks if the given user has a managed profile associated with it.
+ * Checks if the given user has a profile associated with it.
* @param userId The parent user
* @return
*/
- boolean hasManagedProfile(@UserIdInt int userId) {
+ boolean hasProfile(@UserIdInt int userId) {
synchronized (mUsersLock) {
UserInfo userInfo = getUserInfoLU(userId);
final int userSize = mUsers.size();
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 49553f4c91f4..36633cc635e7 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1021,7 +1021,8 @@ final class DefaultPermissionGrantPolicy {
}
for (String packageName : packageNames) {
grantPermissionsToSystemPackage(NO_PM_CACHE, packageName, userId,
- PHONE_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS, SMS_PERMISSIONS);
+ PHONE_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS, SMS_PERMISSIONS,
+ NOTIFICATION_PERMISSIONS);
}
}
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java b/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java
index d3c8c7b57df2..a0bb8dc48ecf 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java
@@ -268,6 +268,9 @@ public interface AndroidPackageApi {
List<FeatureGroupInfo> getFeatureGroups();
@NonNull
+ List<String> getImplicitPermissions();
+
+ @NonNull
List<ParsedInstrumentation> getInstrumentations();
long getLongVersionCode();
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index bd58472f2d2c..914e5eccd192 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1176,14 +1176,14 @@ public final class PowerManagerService extends SystemService
@Override
public void onBootPhase(int phase) {
- synchronized (mLock) {
- if (phase == PHASE_SYSTEM_SERVICES_READY) {
- systemReady();
+ if (phase == PHASE_SYSTEM_SERVICES_READY) {
+ systemReady();
- } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
- incrementBootCount();
+ } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+ incrementBootCount();
- } else if (phase == PHASE_BOOT_COMPLETED) {
+ } else if (phase == PHASE_BOOT_COMPLETED) {
+ synchronized (mLock) {
final long now = mClock.uptimeMillis();
mBootCompleted = true;
mDirty |= DIRTY_BOOT_COMPLETED;
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 2491565dd376..8755662ac813 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -27,6 +27,7 @@ import android.os.IHintSession;
import android.os.Process;
import android.os.RemoteException;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -51,8 +52,13 @@ public final class HintManagerService extends SystemService {
private static final boolean DEBUG = false;
@VisibleForTesting final long mHintSessionPreferredRate;
+ // Multi-levle map storing all active AppHintSessions.
+ // First level is keyed by the UID of the client process creating the session.
+ // Second level is keyed by an IBinder passed from client process. This is used to observe
+ // when the process exits. The client generally uses the same IBinder object across multiple
+ // sessions, so the value is a set of AppHintSessions.
@GuardedBy("mLock")
- private final ArrayMap<Integer, ArrayMap<IBinder, AppHintSession>> mActiveSessions;
+ private final ArrayMap<Integer, ArrayMap<IBinder, ArraySet<AppHintSession>>> mActiveSessions;
/** Lock to protect HAL handles and listen list. */
private final Object mLock = new Object();
@@ -201,13 +207,16 @@ public final class HintManagerService extends SystemService {
public void onUidGone(int uid, boolean disabled) {
FgThread.getHandler().post(() -> {
synchronized (mLock) {
- ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(uid);
+ ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid);
if (tokenMap == null) {
return;
}
for (int i = tokenMap.size() - 1; i >= 0; i--) {
// Will remove the session from tokenMap
- tokenMap.valueAt(i).close();
+ ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(i);
+ for (int j = sessionSet.size() - 1; j >= 0; j--) {
+ sessionSet.valueAt(j).close();
+ }
}
mProcStatesCache.delete(uid);
}
@@ -231,12 +240,14 @@ public final class HintManagerService extends SystemService {
FgThread.getHandler().post(() -> {
synchronized (mLock) {
mProcStatesCache.put(uid, procState);
- ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(uid);
+ ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid);
if (tokenMap == null) {
return;
}
- for (AppHintSession s : tokenMap.values()) {
- s.onProcStateChanged();
+ for (ArraySet<AppHintSession> sessionSet : tokenMap.values()) {
+ for (AppHintSession s : sessionSet) {
+ s.onProcStateChanged();
+ }
}
}
});
@@ -305,17 +316,25 @@ public final class HintManagerService extends SystemService {
long halSessionPtr = mNativeWrapper.halCreateHintSession(callingTgid, callingUid,
tids, durationNanos);
- if (halSessionPtr == 0) return null;
+ if (halSessionPtr == 0) {
+ return null;
+ }
AppHintSession hs = new AppHintSession(callingUid, callingTgid, tids, token,
halSessionPtr, durationNanos);
synchronized (mLock) {
- ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(callingUid);
+ ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
+ mActiveSessions.get(callingUid);
if (tokenMap == null) {
tokenMap = new ArrayMap<>(1);
mActiveSessions.put(callingUid, tokenMap);
}
- tokenMap.put(token, hs);
+ ArraySet<AppHintSession> sessionSet = tokenMap.get(token);
+ if (sessionSet == null) {
+ sessionSet = new ArraySet<>(1);
+ tokenMap.put(token, sessionSet);
+ }
+ sessionSet.add(hs);
return hs;
}
} finally {
@@ -339,10 +358,14 @@ public final class HintManagerService extends SystemService {
pw.println("Active Sessions:");
for (int i = 0; i < mActiveSessions.size(); i++) {
pw.println("Uid " + mActiveSessions.keyAt(i).toString() + ":");
- ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.valueAt(i);
+ ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
+ mActiveSessions.valueAt(i);
for (int j = 0; j < tokenMap.size(); j++) {
- pw.println(" Session " + j + ":");
- tokenMap.valueAt(j).dump(pw, " ");
+ ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(j);
+ for (int k = 0; k < sessionSet.size(); ++k) {
+ pw.println(" Session:");
+ sessionSet.valueAt(k).dump(pw, " ");
+ }
}
}
}
@@ -432,11 +455,18 @@ public final class HintManagerService extends SystemService {
mNativeWrapper.halCloseHintSession(mHalSessionPtr);
mHalSessionPtr = 0;
mToken.unlinkToDeath(this, 0);
- ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(mUid);
+ ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(mUid);
if (tokenMap == null) {
- Slogf.w(TAG, "UID %d is note present in active session map", mUid);
+ Slogf.w(TAG, "UID %d is not present in active session map", mUid);
+ return;
+ }
+ ArraySet<AppHintSession> sessionSet = tokenMap.get(mToken);
+ if (sessionSet == null) {
+ Slogf.w(TAG, "Token %s is not present in token map", mToken.toString());
+ return;
}
- tokenMap.remove(mToken);
+ sessionSet.remove(this);
+ if (sessionSet.isEmpty()) tokenMap.remove(mToken);
if (tokenMap.isEmpty()) mActiveSessions.remove(mUid);
}
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 411f3dcc1eb6..11fd99cf5b68 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -157,7 +157,7 @@ public interface StatusBarManagerInternal {
* @see com.android.internal.statusbar.IStatusBar#requestWindowMagnificationConnection(boolean
* request)
*/
- void requestWindowMagnificationConnection(boolean request);
+ boolean requestWindowMagnificationConnection(boolean request);
/**
* Handles a logging command from the WM shell command.
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index e4a969b3ca1d..59b9daf709d8 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -637,12 +637,14 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
@Override
- public void requestWindowMagnificationConnection(boolean request) {
+ public boolean requestWindowMagnificationConnection(boolean request) {
if (mBar != null) {
try {
mBar.requestWindowMagnificationConnection(request);
+ return true;
} catch (RemoteException ex) { }
}
+ return false;
}
@Override
@@ -856,11 +858,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
@Override
- public void onBiometricAuthenticated() {
+ public void onBiometricAuthenticated(@Modality int modality) {
enforceBiometricDialog();
if (mBar != null) {
try {
- mBar.onBiometricAuthenticated();
+ mBar.onBiometricAuthenticated(modality);
} catch (RemoteException ex) {
}
}
@@ -1952,8 +1954,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
public void setNavBarMode(@NavBarMode int navBarMode) {
enforceStatusBar();
if (navBarMode != NAV_BAR_MODE_DEFAULT && navBarMode != NAV_BAR_MODE_KIDS) {
- throw new UnsupportedOperationException(
- "Supplied navBarMode not supported: " + navBarMode);
+ throw new IllegalArgumentException("Supplied navBarMode not supported: " + navBarMode);
}
final int userId = mCurrentUserId;
@@ -1961,6 +1962,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
try {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.NAV_BAR_KIDS_MODE, navBarMode, userId);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.NAV_BAR_FORCE_VISIBLE, navBarMode, userId);
IOverlayManager overlayManager = getOverlayManager();
if (overlayManager != null && navBarMode == NAV_BAR_MODE_KIDS
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 8f703c5c7761..d3f3abe1085d 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -79,6 +79,7 @@ import android.util.ArraySet;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.TypedValue;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
@@ -138,7 +139,7 @@ final class AccessibilityController {
new SparseArray<>();
private SparseArray<IBinder> mFocusedWindow = new SparseArray<>();
private int mFocusedDisplay = -1;
- private boolean mIsImeVisible = false;
+ private final SparseBooleanArray mIsImeVisibleArray = new SparseBooleanArray();
// Set to true if initializing window population complete.
private boolean mAllObserversInitialized = true;
private final AccessibilityWindowsPopulator mAccessibilityWindowsPopulator;
@@ -167,8 +168,11 @@ final class AccessibilityController {
if (dc != null) {
final Display display = dc.getDisplay();
if (display != null && display.getType() != Display.TYPE_OVERLAY) {
- mDisplayMagnifiers.put(displayId, new DisplayMagnifier(
- mService, dc, display, callbacks));
+ final DisplayMagnifier magnifier = new DisplayMagnifier(
+ mService, dc, display, callbacks);
+ magnifier.notifyImeWindowVisibilityChanged(
+ mIsImeVisibleArray.get(displayId, false));
+ mDisplayMagnifiers.put(displayId, magnifier);
result = true;
}
}
@@ -494,11 +498,13 @@ final class AccessibilityController {
mAccessibilityTracing.logTrace(TAG + ".updateImeVisibilityIfNeeded",
FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId + ";shown=" + shown);
}
- if (mIsImeVisible == shown) {
+
+ final boolean isDisplayImeVisible = mIsImeVisibleArray.get(displayId, false);
+ if (isDisplayImeVisible == shown) {
return;
}
- mIsImeVisible = shown;
+ mIsImeVisibleArray.put(displayId, shown);
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
displayMagnifier.notifyImeWindowVisibilityChanged(shown);
@@ -534,6 +540,7 @@ final class AccessibilityController {
}
public void onDisplayRemoved(int displayId) {
+ mIsImeVisibleArray.delete(displayId);
mFocusedWindow.remove(displayId);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9c910eb730ab..4e751f5a3ab3 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -46,7 +46,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
-import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -7515,8 +7514,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
final int parentWindowingMode =
newParentConfiguration.windowConfiguration.getWindowingMode();
final boolean isFixedOrientationLetterboxAllowed =
- isSplitScreenWindowingMode(parentWindowingMode)
- || parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
+ parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
|| parentWindowingMode == WINDOWING_MODE_FULLSCREEN;
// TODO(b/181207944): Consider removing the if condition and always run
// resolveFixedOrientationConfiguration() since this should be applied for all cases.
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 487aff63b555..23f14a7b7a16 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -53,7 +53,7 @@ class BackNavigationController {
* Returns true if the back predictability feature is enabled
*/
static boolean isEnabled() {
- return SystemProperties.getInt(BACK_PREDICTABILITY_PROP, 1) > 0;
+ return SystemProperties.getInt(BACK_PREDICTABILITY_PROP, 0) > 0;
}
static boolean isScreenshotEnabled() {
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index afa4f190c6e3..08a9da467162 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -24,8 +24,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.activityTypeToString;
import static android.app.WindowConfiguration.windowingModeToString;
import static android.app.WindowConfigurationProto.WINDOWING_MODE;
@@ -475,33 +473,9 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
return WindowConfiguration.inMultiWindowMode(windowingMode);
}
- /** Returns true if this container is currently in split-screen windowing mode. */
- public boolean inSplitScreenWindowingMode() {
- /*@WindowConfiguration.WindowingMode*/ int windowingMode =
- mFullConfiguration.windowConfiguration.getWindowingMode();
-
- return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
- }
-
- /** Returns true if this container is currently in split-screen secondary windowing mode. */
- public boolean inSplitScreenSecondaryWindowingMode() {
- /*@WindowConfiguration.WindowingMode*/ int windowingMode =
- mFullConfiguration.windowConfiguration.getWindowingMode();
-
- return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
- }
-
- public boolean inSplitScreenPrimaryWindowingMode() {
- return mFullConfiguration.windowConfiguration.getWindowingMode()
- == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
- }
-
/**
- * Returns true if this container can be put in either
- * {@link WindowConfiguration#WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} or
- * {@link WindowConfiguration##WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} windowing modes based on
- * its current state.
+ * Returns true if this container supports split-screen multi-window and can be put in
+ * split-screen based on its current state.
*/
public boolean supportsSplitScreenWindowingMode() {
return mFullConfiguration.windowConfiguration.supportSplitScreenWindowingMode();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 1c0687a351e4..82ad861d85cf 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -26,7 +26,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -5521,11 +5520,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final float[] tmpFloat9 = new float[9];
forAllWindows(w -> {
if (w.isVisible() && !w.inPinnedWindowingMode()) {
- if (w.mSession.mSetsUnrestrictedKeepClearAreas) {
- outUnrestricted.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9));
- } else {
- outRestricted.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9));
- }
+ w.getKeepClearAreas(outRestricted, outUnrestricted, tmpMatrix, tmpFloat9);
}
// We stop traversing when we reach the base of a fullscreen app.
@@ -5583,13 +5578,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
static boolean alwaysCreateRootTask(int windowingMode, int activityType) {
- // Always create a root task for fullscreen, freeform, and split-screen-secondary windowing
+ // Always create a root task for fullscreen, freeform, and multi windowing
// modes so that we can manage visual ordering and return types correctly.
return activityType == ACTIVITY_TYPE_STANDARD
&& (windowingMode == WINDOWING_MODE_FULLSCREEN
|| windowingMode == WINDOWING_MODE_FREEFORM
|| windowingMode == WINDOWING_MODE_PINNED
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
|| windowingMode == WINDOWING_MODE_MULTI_WINDOW);
}
@@ -6368,10 +6362,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
/**
- * Returns the fixed orientation requested by a transient launch (e.g. recents animation).
- * If it doesn't return SCREEN_ORIENTATION_UNSET, the rotation change should be deferred.
+ * Returns {@code true} if the transient launch (e.g. recents animation) requested a fixed
+ * orientation, then the rotation change should be deferred.
*/
- @ActivityInfo.ScreenOrientation int getTransientFixedOrientation() {
+ boolean shouldDeferRotation() {
ActivityRecord source = null;
if (mTransitionController.isShellTransitionsEnabled()) {
final ActivityRecord r = mFixedRotationLaunchingApp;
@@ -6383,13 +6377,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
if (source == null || source.getRequestedConfigurationOrientation(
true /* forDisplay */) == ORIENTATION_UNDEFINED) {
- return SCREEN_ORIENTATION_UNSET;
- }
- if (!mWmService.mPolicy.okToAnimate(false /* ignoreScreenOn */)) {
- // If screen is off or the device is going to sleep, then still allow to update.
- return SCREEN_ORIENTATION_UNSET;
+ return false;
}
- return source.mOrientation;
+ // If screen is off or the device is going to sleep, then still allow to update.
+ return mWmService.mPolicy.okToAnimate(false /* ignoreScreenOn */);
}
@Override
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index f116fffa8075..262ddae02765 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -446,12 +446,10 @@ public class DisplayRotation {
return false;
}
- final int transientFixedOrientation =
- mDisplayContent.mFixedRotationTransitionListener.getTransientFixedOrientation();
- if (transientFixedOrientation != SCREEN_ORIENTATION_UNSET) {
+ if (mDisplayContent.mFixedRotationTransitionListener.shouldDeferRotation()) {
// Makes sure that after the transition is finished, updateOrientation() can see
// the difference from the latest orientation source.
- mLastOrientation = transientFixedOrientation;
+ mLastOrientation = SCREEN_ORIENTATION_UNSET;
// During the recents animation, the closing app might still be considered on top.
// In order to ignore its requested orientation to avoid a sensor led rotation (e.g
// user rotating the device while the recents animation is running), we ignore
diff --git a/services/core/java/com/android/server/wm/DragResizeMode.java b/services/core/java/com/android/server/wm/DragResizeMode.java
index d754fd861e27..684cf06e08b8 100644
--- a/services/core/java/com/android/server/wm/DragResizeMode.java
+++ b/services/core/java/com/android/server/wm/DragResizeMode.java
@@ -40,8 +40,6 @@ class DragResizeMode {
switch (mode) {
case DRAG_RESIZE_MODE_FREEFORM:
return rootTask.getWindowingMode() == WINDOWING_MODE_FREEFORM;
- case DRAG_RESIZE_MODE_DOCKED_DIVIDER:
- return rootTask.inSplitScreenWindowingMode();
default:
return false;
}
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index bb6d83c230ac..4fdb1f7930f5 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -449,11 +449,8 @@ class InsetsPolicy {
boolean copyState) {
final WindowState roundedCornerWindow = mPolicy.getRoundedCornerWindow();
final Task task = w.getTask();
- final boolean isInSplitScreenMode = task != null && task.inMultiWindowMode()
- && task.getRootTask() != null
- && task.getRootTask().getAdjacentTaskFragment() != null;
if (task != null && !task.getWindowConfiguration().tasksAreFloating()
- && (roundedCornerWindow != null || isInSplitScreenMode)) {
+ && (roundedCornerWindow != null || task.inSplitScreen())) {
// Instead of using display frame to calculating rounded corner, for the fake rounded
// corners drawn by divider bar or task bar, we need to re-calculate rounded corners
// based on task bounds and if the task bounds is intersected with task bar, we should
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 93714e881441..a407021c7594 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -18,7 +18,6 @@ package com.android.server.wm;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
@@ -610,8 +609,7 @@ public class RecentsAnimationController implements DeathRecipient {
final TaskAnimationAdapter adapter = mPendingAnimations.get(i);
final Task task = adapter.mTask;
final TaskFragment adjacentTask = task.getRootTask().getAdjacentTaskFragment();
- final boolean inSplitScreen = task.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
- && adjacentTask != null;
+ final boolean inSplitScreen = task.inSplitScreen();
if (task.isActivityTypeHomeOrRecents()
// Skip if the task is in split screen and in landscape.
|| (inSplitScreen && isDisplayLandscape)
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index cd8ddf4e9211..bafdd92c058a 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -84,6 +84,7 @@ import com.android.server.LocalServices;
import com.android.server.wm.WindowManagerService.H;
import java.io.PrintWriter;
+import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer;
@@ -521,10 +522,15 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
}
@Override
- public void reportKeepClearAreasChanged(IWindow window, List<Rect> keepClearAreas) {
+ public void reportKeepClearAreasChanged(IWindow window, List<Rect> restricted,
+ List<Rect> unrestricted) {
+ if (!mSetsUnrestrictedKeepClearAreas && !unrestricted.isEmpty()) {
+ unrestricted = Collections.emptyList();
+ }
+
final long ident = Binder.clearCallingIdentity();
try {
- mService.reportKeepClearAreasChanged(this, window, keepClearAreas);
+ mService.reportKeepClearAreasChanged(this, window, restricted, unrestricted);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index f3933c5f7bd6..7fe34f47c53c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -29,6 +29,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.PINNED_WINDOWING_MODE_ELEVATION_IN_DIP;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
@@ -1722,6 +1723,13 @@ class Task extends TaskFragment {
&& (topTask == null || topTask.supportsSplitScreenWindowingModeInner(tda));
}
+ /** Returns {@code true} if this task is currently in split-screen. */
+ boolean inSplitScreen() {
+ return getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
+ && getRootTask() != null
+ && getRootTask().getAdjacentTaskFragment() != null;
+ }
+
private boolean supportsSplitScreenWindowingModeInner(@Nullable TaskDisplayArea tda) {
return super.supportsSplitScreenWindowingMode()
&& mAtmService.mSupportsSplitScreenMultiWindow
@@ -6020,9 +6028,6 @@ class Task extends TaskFragment {
}
boolean shouldIgnoreInput() {
- if (inSplitScreenPrimaryWindowingMode() && !isFocusable()) {
- return true;
- }
if (mAtmService.mHasLeanbackFeature && inPinnedWindowingMode()
&& !isFocusedRootTaskOnDisplay()) {
// Preventing Picture-in-Picture root task from receiving input on TVs.
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 7fab94cab413..afc3087f4ee9 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1767,8 +1767,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
// Resolve override windowing mode to fullscreen for home task (even on freeform
// display), or split-screen if in split-screen mode.
if (getActivityType() == ACTIVITY_TYPE_HOME && windowingMode == WINDOWING_MODE_UNDEFINED) {
- windowingMode = WindowConfiguration.isSplitScreenWindowingMode(parentWindowingMode)
- ? parentWindowingMode : WINDOWING_MODE_FULLSCREEN;
+ windowingMode = WINDOWING_MODE_FULLSCREEN;
getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5eb81872efb1..03e21405ff03 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4412,10 +4412,11 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- void reportKeepClearAreasChanged(Session session, IWindow window, List<Rect> keepClearAreas) {
+ void reportKeepClearAreasChanged(Session session, IWindow window,
+ List<Rect> restricted, List<Rect> unrestricted) {
synchronized (mGlobalLock) {
final WindowState win = windowForClientLocked(session, window, true);
- if (win.setKeepClearAreas(keepClearAreas)) {
+ if (win.setKeepClearAreas(restricted, unrestricted)) {
win.getDisplayContent().updateKeepClearAreas();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 517837c3732a..d547275dcf0c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -22,7 +22,6 @@ import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.AppOpsManager.OP_NONE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.graphics.GraphicsProtos.dumpPointProto;
import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
@@ -181,6 +180,7 @@ import static com.android.server.wm.WindowStateProto.REQUESTED_WIDTH;
import static com.android.server.wm.WindowStateProto.STACK_ID;
import static com.android.server.wm.WindowStateProto.SURFACE_INSETS;
import static com.android.server.wm.WindowStateProto.SURFACE_POSITION;
+import static com.android.server.wm.WindowStateProto.UNRESTRICTED_KEEP_CLEAR_AREAS;
import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
@@ -482,6 +482,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
*/
private final List<Rect> mKeepClearAreas = new ArrayList<>();
+ /**
+ * Like mKeepClearAreas, but the unrestricted ones can be trusted to behave nicely.
+ * Floating windows (like Pip) will be moved away from them without applying restrictions.
+ */
+ private final List<Rect> mUnrestrictedKeepClearAreas = new ArrayList<>();
+
// 0 = left, 1 = right
private final int[] mLastRequestedExclusionHeight = {0, 0};
private final int[] mLastGrantedExclusionHeight = {0, 0};
@@ -1023,51 +1029,86 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
/**
- * @return a list of rects that should ideally not be covered by floating windows like pip.
- * The returned rect coordinates are relative to the display origin.
+ * Collects all restricted and unrestricted keep-clear areas for this window.
+ * Keep-clear areas are rects that should ideally not be covered by floating windows like Pip.
+ * The system is more careful about restricted ones and may apply restrictions to them, while
+ * the unrestricted ones are considered safe.
+ *
+ * @param outRestricted list to add restricted keep-clear areas to
+ * @param outUnrestricted list to add unrestricted keep-clear areas to
*/
- List<Rect> getKeepClearAreas() {
+ void getKeepClearAreas(List<Rect> outRestricted, List<Rect> outUnrestricted) {
final Matrix tmpMatrix = new Matrix();
final float[] tmpFloat9 = new float[9];
- return getKeepClearAreas(tmpMatrix, tmpFloat9);
+ getKeepClearAreas(outRestricted, outUnrestricted, tmpMatrix, tmpFloat9);
}
/**
+ * Collects all restricted and unrestricted keep-clear areas for this window.
+ * Keep-clear areas are rects that should ideally not be covered by floating windows like Pip.
+ * The system is more careful about restricted ones and may apply restrictions to them, while
+ * the unrestricted ones are considered safe.
+ *
+ * @param outRestricted list to add restricted keep-clear areas to
+ * @param outUnrestricted list to add unrestricted keep-clear areas to
* @param tmpMatrix a temporary matrix to be used for transformations
* @param float9 a temporary array of 9 floats
- *
- * @return a list of rects that should ideally not be covered by floating windows like pip.
- * The returned rect coordinates are relative to the display origin.
*/
- List<Rect> getKeepClearAreas(Matrix tmpMatrix, float[] float9) {
+ void getKeepClearAreas(List<Rect> outRestricted, List<Rect> outUnrestricted, Matrix tmpMatrix,
+ float[] float9) {
+ outRestricted.addAll(getRectsInScreenSpace(mKeepClearAreas, tmpMatrix, float9));
+ outUnrestricted.addAll(
+ getRectsInScreenSpace(mUnrestrictedKeepClearAreas, tmpMatrix, float9));
+ }
+
+ /**
+ * Transforms the given rects from window coordinate space to screen space.
+ */
+ List<Rect> getRectsInScreenSpace(List<Rect> rects, Matrix tmpMatrix, float[] float9) {
getTransformationMatrix(float9, tmpMatrix);
- // Translate all keep-clear rects to screen coordinates.
- final List<Rect> transformedKeepClearAreas = new ArrayList<Rect>();
+ final List<Rect> transformedRects = new ArrayList<Rect>();
final RectF tmpRect = new RectF();
Rect curr;
- for (Rect r : mKeepClearAreas) {
+ for (Rect r : rects) {
tmpRect.set(r);
tmpMatrix.mapRect(tmpRect);
curr = new Rect();
tmpRect.roundOut(curr);
- transformedKeepClearAreas.add(curr);
+ transformedRects.add(curr);
}
- return transformedKeepClearAreas;
+ return transformedRects;
}
/**
- * @param keepClearAreas the new keep-clear areas for this window. The rects should be defined
- * in window coordinate space
+ * Sets the new keep-clear areas for this window. The rects should be defined in window
+ * coordinate space.
+ * Keep-clear areas can be restricted or unrestricted, depending on whether the app holds the
+ * {@link android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS} system permission.
+ * Restricted ones will be handled more carefully by the system. Restrictions may be applied.
+ * Unrestricted ones are considered safe. The system should move floating windows away from them
+ * without applying restrictions.
+ *
+ * @param restricted the new restricted keep-clear areas for this window
+ * @param unrestricted the new unrestricted keep-clear areas for this window
*
* @return true if there is a change in the list of keep-clear areas; false otherwise
*/
- boolean setKeepClearAreas(List<Rect> keepClearAreas) {
- if (mKeepClearAreas.equals(keepClearAreas)) {
+ boolean setKeepClearAreas(List<Rect> restricted, List<Rect> unrestricted) {
+ final boolean newRestrictedAreas = !mKeepClearAreas.equals(restricted);
+ final boolean newUnrestrictedAreas = !mUnrestrictedKeepClearAreas.equals(unrestricted);
+ if (!newRestrictedAreas && !newUnrestrictedAreas) {
return false;
}
- mKeepClearAreas.clear();
- mKeepClearAreas.addAll(keepClearAreas);
+ if (newRestrictedAreas) {
+ mKeepClearAreas.clear();
+ mKeepClearAreas.addAll(restricted);
+ }
+
+ if (newUnrestrictedAreas) {
+ mUnrestrictedKeepClearAreas.clear();
+ mUnrestrictedKeepClearAreas.addAll(unrestricted);
+ }
return true;
}
@@ -3582,11 +3623,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
final int requested = mLastRequestedExclusionHeight[side];
final int granted = mLastGrantedExclusionHeight[side];
+ final boolean inSplitScreen = getTask() != null && getTask().inSplitScreen();
+
FrameworkStatsLog.write(FrameworkStatsLog.EXCLUSION_RECT_STATE_CHANGED,
mAttrs.packageName, requested, requested - granted /* rejected */,
side + 1 /* Sides are 1-indexed in atoms.proto */,
(getConfiguration().orientation == ORIENTATION_LANDSCAPE),
- isSplitScreenWindowingMode(getWindowingMode()), (int) duration);
+ inSplitScreen, (int) duration);
}
private void initExclusionRestrictions() {
@@ -4121,8 +4164,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (task == null) {
return false;
}
- if (!inSplitScreenWindowingMode() && !inFreeformWindowingMode()
- && !task.getRootTask().mCreatedByOrganizer) {
+ if (!inFreeformWindowingMode() && !task.getRootTask().mCreatedByOrganizer) {
return false;
}
// TODO(157912944): formalize drag-resizing so that exceptions aren't hardcoded like this
@@ -4202,9 +4244,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
proto.write(FORCE_SEAMLESS_ROTATION, mForceSeamlesslyRotate);
proto.write(HAS_COMPAT_SCALE, hasCompatScale());
proto.write(GLOBAL_SCALE, mGlobalScale);
- for (Rect r : getKeepClearAreas()) {
+ for (Rect r : mKeepClearAreas) {
r.dumpDebug(proto, KEEP_CLEAR_AREAS);
}
+ for (Rect r : mUnrestrictedKeepClearAreas) {
+ r.dumpDebug(proto, UNRESTRICTED_KEEP_CLEAR_AREAS);
+ }
proto.end(token);
}
@@ -4373,7 +4418,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
pw.println(prefix + "isOnScreen=" + isOnScreen());
pw.println(prefix + "isVisible=" + isVisible());
- pw.println(prefix + "keepClearAreas=" + getKeepClearAreas());
+ pw.println(prefix + "keepClearAreas: restricted=" + mKeepClearAreas
+ + ", unrestricted=" + mUnrestrictedKeepClearAreas);
if (dumpAll) {
final String visibilityString = mRequestedVisibilities.toString();
if (!visibilityString.isEmpty()) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index 26b5218d2ab4..4a40b5f2de7b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -1000,7 +1000,7 @@ public class ApplicationExitInfoTest {
final String dummyPackageName = "com.android.test";
final String dummyClassName = ".Foo";
app.setHostingRecord(HostingRecord.byAppZygote(new ComponentName(
- dummyPackageName, dummyClassName), "", definingUid));
+ dummyPackageName, dummyClassName), "", definingUid, ""));
}
app.mServices.setConnectionGroup(connectionGroup);
app.mState.setReportedProcState(procState);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 953b5368c86f..1f016fb6f017 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -154,6 +154,7 @@ public class AccessibilityManagerServiceTest {
mMockWindowMagnificationMgr);
when(mMockMagnificationController.getFullScreenMagnificationController()).thenReturn(
mMockFullScreenMagnificationController);
+ when(mMockMagnificationController.supportWindowMagnification()).thenReturn(true);
when(mMockWindowManagerService.getAccessibilityController()).thenReturn(
mMockA11yController);
when(mMockA11yController.isAccessibilityTracingEnabled()).thenReturn(false);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index 3ce2ed84d3e8..f3a0b7fa1ea7 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -1158,7 +1158,7 @@ public class FullScreenMagnificationControllerTest {
MagnificationCallbacks callbacks = getMagnificationCallbacks(DISPLAY_0);
callbacks.onImeWindowVisibilityChanged(true);
mMessageCapturingHandler.sendAllMessages();
- verify(mRequestObserver).onImeWindowVisibilityChanged(eq(true));
+ verify(mRequestObserver).onImeWindowVisibilityChanged(eq(DISPLAY_0), eq(true));
}
private void setScaleToMagnifying() {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index ec59090240f3..cc6d7611b4a1 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -41,6 +41,7 @@ import static org.mockito.Mockito.when;
import android.accessibilityservice.MagnificationConfig;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
@@ -100,6 +101,8 @@ public class MagnificationControllerTest {
@Mock
private Context mContext;
@Mock
+ PackageManager mPackageManager;
+ @Mock
private FullScreenMagnificationController mScreenMagnificationController;
private MagnificationScaleProvider mScaleProvider;
@Captor
@@ -136,6 +139,7 @@ public class MagnificationControllerTest {
mMockResolver = new MockContentResolver();
mMockResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
when(mContext.getContentResolver()).thenReturn(mMockResolver);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
Settings.Secure.putFloatForUser(mMockResolver,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, DEFAULT_SCALE,
CURRENT_USER_ID);
@@ -748,7 +752,7 @@ public class MagnificationControllerTest {
MagnificationController spyController = spy(mMagnificationController);
spyController.onWindowMagnificationActivationState(TEST_DISPLAY, true);
- spyController.onImeWindowVisibilityChanged(true);
+ spyController.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
verify(spyController).logMagnificationModeWithIme(
eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW));
@@ -759,7 +763,7 @@ public class MagnificationControllerTest {
MagnificationController spyController = spy(mMagnificationController);
spyController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true);
- spyController.onImeWindowVisibilityChanged(true);
+ spyController.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
verify(spyController).logMagnificationModeWithIme(
eq(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN));
@@ -768,7 +772,7 @@ public class MagnificationControllerTest {
@Test
public void imeWindowStateShown_noMagnifying_noLogAnyMode() {
MagnificationController spyController = spy(mMagnificationController);
- spyController.onImeWindowVisibilityChanged(true);
+ spyController.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
verify(spyController, never()).logMagnificationModeWithIme(anyInt());
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
index 3822dc362b6b..4b77764c41e5 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
@@ -94,6 +94,14 @@ public class WindowMagnificationConnectionWrapperTest {
}
@Test
+ public void moveWindowMagnifierToPosition() throws RemoteException {
+ mConnectionWrapper.moveWindowMagnifierToPosition(TEST_DISPLAY, 100, 150,
+ mAnimationCallback);
+ verify(mConnection).moveWindowMagnifierToPosition(eq(TEST_DISPLAY),
+ eq(100f), eq(150f), any(IRemoteMagnificationAnimationCallback.class));
+ }
+
+ @Test
public void showMagnificationButton() throws RemoteException {
mConnectionWrapper.showMagnificationButton(TEST_DISPLAY,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index 0742c09492f2..978000aa89d3 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -21,6 +21,8 @@ import static com.android.server.accessibility.magnification.MockWindowMagnifica
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.doAnswer;
@@ -54,6 +56,8 @@ import android.view.accessibility.IRemoteMagnificationAnimationCallback;
import android.view.accessibility.IWindowMagnificationConnectionCallback;
import android.view.accessibility.MagnificationAnimationCallback;
+import androidx.test.core.app.ApplicationProvider;
+
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityTraceManager;
@@ -99,12 +103,7 @@ public class WindowMagnificationManagerTest {
mMockCallback, mMockTrace, new MagnificationScaleProvider(mContext));
when(mContext.getContentResolver()).thenReturn(mResolver);
- doAnswer((InvocationOnMock invocation) -> {
- final boolean connect = (Boolean) invocation.getArguments()[0];
- mWindowMagnificationManager.setConnection(
- connect ? mMockConnection.getConnection() : null);
- return null;
- }).when(mMockStatusBarManagerInternal).requestWindowMagnificationConnection(anyBoolean());
+ stubSetConnection(false);
mResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
Settings.Secure.putFloatForUser(mResolver,
@@ -112,6 +111,25 @@ public class WindowMagnificationManagerTest {
CURRENT_USER_ID);
}
+ private void stubSetConnection(boolean needDelay) {
+ doAnswer((InvocationOnMock invocation) -> {
+ final boolean connect = (Boolean) invocation.getArguments()[0];
+ // Simulates setConnection() called by another process.
+ if (needDelay) {
+ final Context context = ApplicationProvider.getApplicationContext();
+ context.getMainThreadHandler().postDelayed(
+ () -> {
+ mWindowMagnificationManager.setConnection(
+ connect ? mMockConnection.getConnection() : null);
+ }, 10);
+ } else {
+ mWindowMagnificationManager.setConnection(
+ connect ? mMockConnection.getConnection() : null);
+ }
+ return true;
+ }).when(mMockStatusBarManagerInternal).requestWindowMagnificationConnection(anyBoolean());
+ }
+
@Test
public void setConnection_connectionIsNull_wrapperIsNullAndLinkToDeath() {
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
@@ -275,32 +293,33 @@ public class WindowMagnificationManagerTest {
}
@Test
- public void onRectangleOnScreenRequested_trackingDisabledByOnDrag_withoutMovingMagnification()
+ public void onRectangleOnScreenRequested_trackingDisabledByOnDrag_withoutMovingMagnifier()
throws RemoteException {
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+ mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
final Region outRegion = new Region();
mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
final Rect requestedRect = outRegion.getBounds();
requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
- mMockConnection.getConnectionCallback().onDrag(TEST_DISPLAY);
+ mMockConnection.getConnectionCallback().onMove(TEST_DISPLAY);
mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
- verify(mMockConnection.getConnection(), never()).enableWindowMagnification(eq(TEST_DISPLAY),
- eq(3f), eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
- eq(0f), eq(0f), notNull());
+ verify(mMockConnection.getConnection(), never())
+ .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
}
@Test
- public void onRectangleOnScreenRequested_trackingDisabledByScroll_withoutMovingMagnification()
+ public void onRectangleOnScreenRequested_trackingDisabledByScroll_withoutMovingMagnifier()
throws RemoteException {
final float distanceX = 10f;
final float distanceY = 10f;
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+ mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
final Region outRegion = new Region();
mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
final Rect requestedRect = outRegion.getBounds();
@@ -310,16 +329,16 @@ public class WindowMagnificationManagerTest {
mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
- verify(mMockConnection.getConnection(), never()).enableWindowMagnification(eq(TEST_DISPLAY),
- eq(3f), eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
- eq(0f), eq(0f), notNull());
+ verify(mMockConnection.getConnection(), never())
+ .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
}
@Test
- public void onRectangleOnScreenRequested_requestRectangleInBound_withoutMovingMagnification()
+ public void onRectangleOnScreenRequested_requestRectangleInBound_withoutMovingMagnifier()
throws RemoteException {
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+ mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
final Region outRegion = new Region();
mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
final Rect requestedRect = outRegion.getBounds();
@@ -328,15 +347,32 @@ public class WindowMagnificationManagerTest {
mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
- verify(mMockConnection.getConnection(), never()).enableWindowMagnification(eq(TEST_DISPLAY),
- eq(3f), eq(500f), eq(500f), eq(0f), eq(0f), notNull());
+ verify(mMockConnection.getConnection(), never())
+ .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
+ }
+ @Test
+ public void onRectangleOnScreenRequested_imeVisibilityDefaultInvisible_withoutMovingMagnifier()
+ throws RemoteException {
+ mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+ mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+ final Region outRegion = new Region();
+ mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
+ final Rect requestedRect = outRegion.getBounds();
+ requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
+
+ mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
+ requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
+
+ verify(mMockConnection.getConnection(), never())
+ .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
}
@Test
- public void onRectangleOnScreenRequested_trackingEnabledByDefault_movingMagnification()
+ public void onRectangleOnScreenRequested_trackingEnabledByDefault_movingMagnifier()
throws RemoteException {
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+ mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
final Region outRegion = new Region();
mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
final Rect requestedRect = outRegion.getBounds();
@@ -345,38 +381,56 @@ public class WindowMagnificationManagerTest {
mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
- verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(3f),
+ verify(mMockConnection.getConnection()).moveWindowMagnifierToPosition(eq(TEST_DISPLAY),
eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
- eq(0f), eq(0f), notNull());
+ any(IRemoteMagnificationAnimationCallback.class));
}
@Test
- public void onRectangleOnScreenRequested_trackingEnabledByDragAndReset_movingMagnification()
+ public void onRectangleOnScreenRequested_imeInvisible_withoutMovingMagnifier()
throws RemoteException {
- final PointF initialPoint = new PointF(50f, 50f);
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
- mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f,
- initialPoint.x, initialPoint.y);
- mMockConnection.getConnectionCallback().onDrag(TEST_DISPLAY);
- mWindowMagnificationManager.onImeWindowVisibilityChanged(true);
+ mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+ mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
final Region outRegion = new Region();
mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
final Rect requestedRect = outRegion.getBounds();
requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
+ mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, false);
mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
- verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY),
- eq(3f), eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
- eq(0f), eq(0f), notNull());
+ verify(mMockConnection.getConnection(), never())
+ .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
}
@Test
- public void onRectangleOnScreenRequested_followTypingIsDisabled_withoutMovingMagnification()
+ public void onRectangleOnScreenRequested_trackingEnabledByDragAndReset_movingMagnifier()
throws RemoteException {
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+ mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+ mMockConnection.getConnectionCallback().onMove(TEST_DISPLAY);
+ mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+ final Region outRegion = new Region();
+ mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
+ final Rect requestedRect = outRegion.getBounds();
+ requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
+
+ mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
+ requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
+
+ verify(mMockConnection.getConnection()).moveWindowMagnifierToPosition(eq(TEST_DISPLAY),
+ eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
+ any(IRemoteMagnificationAnimationCallback.class));
+ }
+
+ @Test
+ public void onRectangleOnScreenRequested_followTypingIsDisabled_withoutMovingMagnifier() {
+ mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+ mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+ mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
final Region beforeRegion = new Region();
mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion);
final Rect requestedRect = beforeRegion.getBounds();
@@ -392,6 +446,48 @@ public class WindowMagnificationManagerTest {
}
@Test
+ public void onRectangleOnScreenRequested_trackingDisabled_withoutMovingMagnifier() {
+ mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+ mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+ mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+ mWindowMagnificationManager.setTrackingTypingFocusEnabled(TEST_DISPLAY, false);
+ final Region beforeRegion = new Region();
+ mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion);
+ final Rect requestedRect = beforeRegion.getBounds();
+ requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
+
+ mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
+ requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
+
+ final Region afterRegion = new Region();
+ mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, afterRegion);
+ assertEquals(afterRegion, beforeRegion);
+ }
+
+ @Test
+ public void onRectangleOnScreenRequested_trackingDisabledAndEnabledMagnifier_movingMagnifier()
+ throws RemoteException {
+ mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+ mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+ mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+ mWindowMagnificationManager.setTrackingTypingFocusEnabled(TEST_DISPLAY, false);
+ final Region beforeRegion = new Region();
+ mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion);
+ final Rect requestedRect = beforeRegion.getBounds();
+ requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
+ mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false);
+ // Enabling a window magnifier again will turn on the tracking typing focus functionality.
+ mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, NaN, NaN, NaN);
+
+ mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
+ requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
+
+ verify(mMockConnection.getConnection()).moveWindowMagnifierToPosition(eq(TEST_DISPLAY),
+ eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
+ any(IRemoteMagnificationAnimationCallback.class));
+ }
+
+ @Test
public void moveWindowMagnifier_enabled_invokeConnectionMethod() throws RemoteException {
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2f, NaN, NaN);
@@ -464,7 +560,7 @@ public class WindowMagnificationManagerTest {
public void
requestConnectionToNull_disableAllMagnifiersAndRequestWindowMagnificationConnection()
throws RemoteException {
- mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+ assertTrue(mWindowMagnificationManager.requestConnection(true));
mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN);
assertTrue(mWindowMagnificationManager.requestConnection(false));
@@ -499,7 +595,7 @@ public class WindowMagnificationManagerTest {
@Test
public void requestConnectionToNull_expectedGetterResults() {
- mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+ mWindowMagnificationManager.requestConnection(true);
mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, 1, 1);
mWindowMagnificationManager.requestConnection(false);
@@ -513,6 +609,20 @@ public class WindowMagnificationManagerTest {
}
@Test
+ public void enableWindowMagnification_connecting_invokeConnectionMethodAfterConnected()
+ throws RemoteException {
+ stubSetConnection(true);
+ mWindowMagnificationManager.requestConnection(true);
+
+ assertTrue(mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, 1, 1));
+
+ // Invoke enableWindowMagnification if the connection is connected.
+ verify(mMockConnection.getConnection()).enableWindowMagnification(
+ eq(TEST_DISPLAY), eq(3f),
+ eq(1f), eq(1f), eq(0f), eq(0f), notNull());
+ }
+
+ @Test
public void resetAllMagnification_enabledBySameId_windowMagnifiersDisabled() {
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f,
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 b255a35c512e..25cf8a86baad 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -219,7 +219,7 @@ public class AuthSessionTest {
public void testMultiAuth_singleSensor_fingerprintSensorStartsAfterDialogAnimationCompletes()
throws Exception {
setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
- testMultiAuth_fingerprintSensorStartsAfter(false /* fingerprintStartsAfterDelay */);
+ testMultiAuth_fingerprintSensorStartsAfterUINotifies();
}
@Test
@@ -227,10 +227,10 @@ public class AuthSessionTest {
throws Exception {
setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
setupFace(1 /* id */, false, mock(IBiometricAuthenticator.class));
- testMultiAuth_fingerprintSensorStartsAfter(true /* fingerprintStartsAfterDelay */);
+ testMultiAuth_fingerprintSensorStartsAfterUINotifies();
}
- public void testMultiAuth_fingerprintSensorStartsAfter(boolean fingerprintStartsAfterDelay)
+ public void testMultiAuth_fingerprintSensorStartsAfterUINotifies()
throws Exception {
final long operationId = 123;
final int userId = 10;
@@ -274,12 +274,6 @@ public class AuthSessionTest {
// Notify AuthSession that the UI is shown. Then, fingerprint sensor should be started.
session.onDialogAnimatedIn();
- if (fingerprintStartsAfterDelay) {
- assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState());
- assertEquals(BiometricSensor.STATE_COOKIE_RETURNED,
- session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState());
- session.onStartFingerprint();
- }
assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState());
assertEquals(BiometricSensor.STATE_AUTHENTICATING,
session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState());
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 b94b6908f030..2ad5eaeb9aaf 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
@@ -85,14 +86,11 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Random;
-import java.util.concurrent.atomic.AtomicLong;
@Presubmit
@SmallTest
public class BiometricServiceTest {
- private static final String TAG = "BiometricServiceTest";
-
private static final String TEST_PACKAGE_NAME = "test_package";
private static final long TEST_REQUEST_ID = 44;
@@ -153,7 +151,7 @@ public class BiometricServiceTest {
.thenReturn(mock(BiometricStrengthController.class));
when(mInjector.getTrustManager()).thenReturn(mTrustManager);
when(mInjector.getDevicePolicyManager(any())).thenReturn(mDevicePolicyManager);
- when(mInjector.getRequestGenerator()).thenReturn(new AtomicLong(TEST_REQUEST_ID - 1));
+ when(mInjector.getRequestGenerator()).thenReturn(() -> TEST_REQUEST_ID);
when(mResources.getString(R.string.biometric_error_hw_unavailable))
.thenReturn(ERROR_HW_UNAVAILABLE);
@@ -178,22 +176,22 @@ public class BiometricServiceTest {
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
true /* requireConfirmation */, null /* authenticators */);
waitForIdle();
- verify(mReceiver1.asBinder()).linkToDeath(eq(mBiometricService.mCurrentAuthSession),
+ verify(mReceiver1.asBinder()).linkToDeath(eq(mBiometricService.mAuthSession),
anyInt());
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FACE,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
0 /* vendorCode */);
waitForIdle();
- assertEquals(STATE_AUTH_PAUSED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_PAUSED, mBiometricService.mAuthSession.getState());
- mBiometricService.mCurrentAuthSession.binderDied();
+ mBiometricService.mAuthSession.binderDied();
waitForIdle();
- assertNull(mBiometricService.mCurrentAuthSession);
+ assertNull(mBiometricService.mAuthSession);
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
}
@@ -205,31 +203,31 @@ public class BiometricServiceTest {
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
true /* requireConfirmation */, null /* authenticators */);
waitForIdle();
- verify(mReceiver1.asBinder()).linkToDeath(eq(mBiometricService.mCurrentAuthSession),
+ verify(mReceiver1.asBinder()).linkToDeath(eq(mBiometricService.mAuthSession),
anyInt());
- assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
- mBiometricService.mCurrentAuthSession.binderDied();
+ assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
+ mBiometricService.mAuthSession.binderDied();
waitForIdle();
- assertNotNull(mBiometricService.mCurrentAuthSession);
+ assertNotNull(mBiometricService.mAuthSession);
verify(mBiometricService.mStatusBarService, never()).hideAuthenticationDialog();
assertEquals(STATE_CLIENT_DIED_CANCELLING,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
- verify(mBiometricService.mCurrentAuthSession.mPreAuthInfo.eligibleSensors.get(0).impl)
+ verify(mBiometricService.mAuthSession.mPreAuthInfo.eligibleSensors.get(0).impl)
.cancelAuthenticationFromService(any(), any(), anyLong());
// Simulate ERROR_CANCELED received from HAL
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FACE,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
0 /* vendorCode */);
waitForIdle();
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
- assertNull(mBiometricService.mCurrentAuthSession);
+ assertNull(mBiometricService.mAuthSession);
}
@Test
@@ -265,12 +263,12 @@ public class BiometricServiceTest {
Authenticators.DEVICE_CREDENTIAL);
waitForIdle();
- assertNotNull(mBiometricService.mCurrentAuthSession);
+ assertNotNull(mBiometricService.mAuthSession);
assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
// StatusBar showBiometricDialog invoked
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
- eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+ eq(mBiometricService.mAuthSession.mPromptInfo),
any(IBiometricSysuiReceiver.class),
AdditionalMatchers.aryEq(new int[0]) /* sensorIds */,
eq(true) /* credentialAllowed */,
@@ -304,21 +302,21 @@ public class BiometricServiceTest {
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
mBiometricService.mImpl.registerAuthenticator(0 /* id */,
- BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
mFingerprintAuthenticator);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
null /* authenticators */);
waitForIdle();
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ eq(TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS),
eq(0 /* vendorCode */));
}
@Test
public void testAuthenticate_notStrongEnough_returnsHardwareNotPresent() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
Authenticators.BIOMETRIC_STRONG);
@@ -335,7 +333,7 @@ public class BiometricServiceTest {
// is able to proceed.
final int[] modalities = new int[] {
- BiometricAuthenticator.TYPE_FINGERPRINT,
+ TYPE_FINGERPRINT,
BiometricAuthenticator.TYPE_FACE,
};
@@ -356,7 +354,7 @@ public class BiometricServiceTest {
// StatusBar showBiometricDialog invoked with face, which was set up to be STRONG
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
- eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+ eq(mBiometricService.mAuthSession.mPromptInfo),
any(IBiometricSysuiReceiver.class),
AdditionalMatchers.aryEq(new int[] {SENSOR_ID_FACE}),
eq(false) /* credentialAllowed */,
@@ -377,14 +375,14 @@ public class BiometricServiceTest {
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
mBiometricService.mImpl.registerAuthenticator(0 /* id */,
- BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
mFingerprintAuthenticator);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
null /* authenticators */);
waitForIdle();
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ eq(TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE),
eq(0 /* vendorCode */));
}
@@ -415,13 +413,13 @@ public class BiometricServiceTest {
waitForIdle();
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
final byte[] HAT = generateRandomHAT();
- mBiometricService.mBiometricSensorReceiver.onAuthenticationSucceeded(
+ mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded(
SENSOR_ID_FACE,
HAT);
waitForIdle();
// Confirmation is required
assertEquals(STATE_AUTH_PENDING_CONFIRM,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
// Enrolled, not disabled in settings, user doesn't require confirmation in settings
resetReceivers();
@@ -431,25 +429,25 @@ public class BiometricServiceTest {
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
null /* authenticators */);
waitForIdle();
- mBiometricService.mBiometricSensorReceiver.onAuthenticationSucceeded(
+ mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded(
SENSOR_ID_FACE,
HAT);
waitForIdle();
// Confirmation not required, waiting for dialog to dismiss
assertEquals(STATE_AUTHENTICATED_PENDING_SYSUI,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
}
@Test
public void testAuthenticate_happyPathWithoutConfirmation_strongBiometric() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
testAuthenticate_happyPathWithoutConfirmation(true /* isStrongBiometric */);
}
@Test
public void testAuthenticate_happyPathWithoutConfirmation_weakBiometric() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
testAuthenticate_happyPathWithoutConfirmation(false /* isStrongBiometric */);
}
@@ -461,7 +459,7 @@ public class BiometricServiceTest {
waitForIdle();
// Creates a pending auth session with the correct initial states
- assertEquals(STATE_AUTH_CALLED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_CALLED, mBiometricService.mAuthSession.getState());
// Invokes <Modality>Service#prepareForAuthentication
ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -477,19 +475,19 @@ public class BiometricServiceTest {
cookieCaptor.capture() /* cookie */,
anyBoolean() /* allowBackgroundAuthentication */);
- // onReadyForAuthentication, mCurrentAuthSession state OK
- mBiometricService.mImpl.onReadyForAuthentication(cookieCaptor.getValue());
+ // onReadyForAuthentication, mAuthSession state OK
+ mBiometricService.mImpl.onReadyForAuthentication(TEST_REQUEST_ID, cookieCaptor.getValue());
waitForIdle();
- assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
// startPreparedClient invoked
- mBiometricService.mCurrentAuthSession.onDialogAnimatedIn();
+ mBiometricService.mAuthSession.onDialogAnimatedIn();
verify(mBiometricService.mSensors.get(0).impl)
.startPreparedClient(cookieCaptor.getValue());
// StatusBar showBiometricDialog invoked
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
- eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+ eq(mBiometricService.mAuthSession.mPromptInfo),
any(IBiometricSysuiReceiver.class),
any(),
eq(false) /* credentialAllowed */,
@@ -502,18 +500,18 @@ public class BiometricServiceTest {
// Hardware authenticated
final byte[] HAT = generateRandomHAT();
- mBiometricService.mBiometricSensorReceiver.onAuthenticationSucceeded(
+ mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded(
SENSOR_ID_FINGERPRINT,
HAT);
waitForIdle();
// Waiting for SystemUI to send dismissed callback
assertEquals(STATE_AUTHENTICATED_PENDING_SYSUI,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
// Notify SystemUI hardware authenticated
- verify(mBiometricService.mStatusBarService).onBiometricAuthenticated();
+ verify(mBiometricService.mStatusBarService).onBiometricAuthenticated(TYPE_FINGERPRINT);
// SystemUI sends callback with dismissed reason
- mBiometricService.mSysuiReceiver.onDialogDismissed(
+ mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED,
null /* credentialAttestation */);
waitForIdle();
@@ -527,7 +525,7 @@ public class BiometricServiceTest {
verify(mReceiver1).onAuthenticationSucceeded(
BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
// Current session becomes null
- assertNull(mBiometricService.mCurrentAuthSession);
+ assertNull(mBiometricService.mAuthSession);
}
@Test
@@ -542,11 +540,11 @@ public class BiometricServiceTest {
waitForIdle();
assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
assertEquals(Authenticators.DEVICE_CREDENTIAL,
- mBiometricService.mCurrentAuthSession.mPromptInfo.getAuthenticators());
+ mBiometricService.mAuthSession.mPromptInfo.getAuthenticators());
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
- eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+ eq(mBiometricService.mAuthSession.mPromptInfo),
any(IBiometricSysuiReceiver.class),
AdditionalMatchers.aryEq(new int[0]) /* sensorIds */,
eq(true) /* credentialAllowed */,
@@ -578,16 +576,16 @@ public class BiometricServiceTest {
// Test authentication succeeded goes to PENDING_CONFIRMATION and that the HAT is not
// sent to KeyStore yet
final byte[] HAT = generateRandomHAT();
- mBiometricService.mBiometricSensorReceiver.onAuthenticationSucceeded(
+ mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded(
SENSOR_ID_FACE,
HAT);
waitForIdle();
// Waiting for SystemUI to send confirmation callback
- assertEquals(STATE_AUTH_PENDING_CONFIRM, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_PENDING_CONFIRM, mBiometricService.mAuthSession.getState());
verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
// SystemUI sends confirm, HAT is sent to keystore and client is notified.
- mBiometricService.mSysuiReceiver.onDialogDismissed(
+ mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED,
null /* credentialAttestation */);
waitForIdle();
@@ -624,33 +622,34 @@ public class BiometricServiceTest {
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
- mBiometricService.mBiometricSensorReceiver.onAuthenticationFailed(SENSOR_ID_FACE);
+ mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationFailed(SENSOR_ID_FACE);
waitForIdle();
verify(mBiometricService.mStatusBarService).onBiometricError(
- eq(BiometricAuthenticator.TYPE_NONE),
+ eq(BiometricAuthenticator.TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_PAUSED_REJECTED),
eq(0 /* vendorCode */));
verify(mReceiver1).onAuthenticationFailed();
- assertEquals(STATE_AUTH_PAUSED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_PAUSED, mBiometricService.mAuthSession.getState());
}
@Test
public void testRejectFingerprint_whenAuthenticating_notifiesAndKeepsAuthenticating()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
- mBiometricService.mBiometricSensorReceiver.onAuthenticationFailed(SENSOR_ID_FINGERPRINT);
+ mBiometricService.mAuthSession.mSensorReceiver
+ .onAuthenticationFailed(SENSOR_ID_FINGERPRINT);
waitForIdle();
verify(mBiometricService.mStatusBarService).onBiometricError(
- eq(BiometricAuthenticator.TYPE_NONE),
+ eq(TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_PAUSED_REJECTED),
eq(0 /* vendorCode */));
verify(mReceiver1).onAuthenticationFailed();
- assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
}
@Test
@@ -678,14 +677,14 @@ public class BiometricServiceTest {
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FACE,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
0 /* vendorCode */);
waitForIdle();
- assertEquals(STATE_AUTH_PAUSED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_PAUSED, mBiometricService.mAuthSession.getState());
verify(mBiometricService.mStatusBarService).onBiometricError(
eq(BiometricAuthenticator.TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_TIMEOUT),
@@ -694,15 +693,15 @@ public class BiometricServiceTest {
verify(mReceiver1, never()).onAuthenticationFailed();
// No auth session. Pressing try again will create one.
- assertEquals(STATE_AUTH_PAUSED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_PAUSED, mBiometricService.mAuthSession.getState());
// Pressing "Try again" on SystemUI
- mBiometricService.mSysuiReceiver.onTryAgainPressed();
+ mBiometricService.mAuthSession.mSysuiReceiver.onTryAgainPressed();
waitForIdle();
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
// AuthSession is now resuming
- assertEquals(STATE_AUTH_PAUSED_RESUMING, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_PAUSED_RESUMING, mBiometricService.mAuthSession.getState());
// Test resuming when hardware becomes ready. SystemUI should not be requested to
// show another dialog since it's already showing.
@@ -728,14 +727,14 @@ public class BiometricServiceTest {
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FACE,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
0 /* vendorCode */);
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FACE,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
0 /* vendorCode */);
waitForIdle();
@@ -748,7 +747,7 @@ public class BiometricServiceTest {
// Dialog is hidden immediately
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
// Auth session is over
- assertNull(mBiometricService.mCurrentAuthSession);
+ assertNull(mBiometricService.mAuthSession);
}
@Test
@@ -757,61 +756,61 @@ public class BiometricServiceTest {
// For errors that show in SystemUI, BiometricService stays in STATE_ERROR_PENDING_SYSUI
// until SystemUI notifies us that the dialog is dismissed at which point the current
// session is done.
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FINGERPRINT,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS,
0 /* vendorCode */);
waitForIdle();
// Sends error to SystemUI and does not notify client yet
- assertEquals(STATE_ERROR_PENDING_SYSUI, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_ERROR_PENDING_SYSUI, mBiometricService.mAuthSession.getState());
verify(mBiometricService.mStatusBarService).onBiometricError(
- eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ eq(TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
eq(0 /* vendorCode */));
verify(mBiometricService.mStatusBarService, never()).hideAuthenticationDialog();
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
// SystemUI animation completed, client is notified, auth session is over
- mBiometricService.mSysuiReceiver.onDialogDismissed(
+ mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_ERROR, null /* credentialAttestation */);
waitForIdle();
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ eq(TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
eq(0 /* vendorCode */));
- assertNull(mBiometricService.mCurrentAuthSession);
+ assertNull(mBiometricService.mAuthSession);
}
@Test
public void testErrorFromHal_whilePreparingAuthentication_credentialAllowed() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */,
Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
waitForIdle();
- assertEquals(STATE_AUTH_CALLED, mBiometricService.mCurrentAuthSession.getState());
- mBiometricService.mBiometricSensorReceiver.onError(
+ assertEquals(STATE_AUTH_CALLED, mBiometricService.mAuthSession.getState());
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FINGERPRINT,
- getCookieForPendingSession(mBiometricService.mCurrentAuthSession),
+ getCookieForPendingSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
0 /* vendorCode */);
waitForIdle();
// We should be showing device credential now
- assertNotNull(mBiometricService.mCurrentAuthSession);
+ assertNotNull(mBiometricService.mAuthSession);
assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
assertEquals(Authenticators.DEVICE_CREDENTIAL,
- mBiometricService.mCurrentAuthSession.mPromptInfo.getAuthenticators());
+ mBiometricService.mAuthSession.mPromptInfo.getAuthenticators());
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
- eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+ eq(mBiometricService.mAuthSession.mPromptInfo),
any(IBiometricSysuiReceiver.class),
AdditionalMatchers.aryEq(new int[0]) /* sensorIds */,
eq(true) /* credentialAllowed */,
@@ -826,23 +825,23 @@ public class BiometricServiceTest {
@Test
public void testErrorFromHal_whilePreparingAuthentication_credentialNotAllowed()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
waitForIdle();
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FINGERPRINT,
- getCookieForPendingSession(mBiometricService.mCurrentAuthSession),
+ getCookieForPendingSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
0 /* vendorCode */);
waitForIdle();
// Error is sent to client
- verify(mReceiver1).onError(eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ verify(mReceiver1).onError(eq(TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_LOCKOUT),
eq(0) /* vendorCode */);
- assertNull(mBiometricService.mCurrentAuthSession);
+ assertNull(mBiometricService.mAuthSession);
}
@Test
@@ -861,7 +860,7 @@ public class BiometricServiceTest {
private void testBiometricAuth_whenLockout(@LockoutTracker.LockoutMode int lockoutMode,
int biometricPromptError) throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
.thenReturn(lockoutMode);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
@@ -869,16 +868,15 @@ public class BiometricServiceTest {
waitForIdle();
// Modality and error are sent
- verify(mReceiver1).onError(eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ 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);
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
.thenReturn(LockoutTracker.LOCKOUT_PERMANENT);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
@@ -887,13 +885,13 @@ public class BiometricServiceTest {
waitForIdle();
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
- assertNotNull(mBiometricService.mCurrentAuthSession);
+ assertNotNull(mBiometricService.mAuthSession);
assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
assertEquals(Authenticators.DEVICE_CREDENTIAL,
- mBiometricService.mCurrentAuthSession.mPromptInfo.getAuthenticators());
+ mBiometricService.mAuthSession.mPromptInfo.getAuthenticators());
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
- eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+ eq(mBiometricService.mAuthSession.mPromptInfo),
any(IBiometricSysuiReceiver.class),
AdditionalMatchers.aryEq(new int[0]) /* sensorIds */,
eq(true) /* credentialAllowed */,
@@ -959,73 +957,73 @@ public class BiometricServiceTest {
@Test
public void testErrorFromHal_whileShowingDeviceCredential_doesntNotifySystemUI()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */,
Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
- mBiometricService.mSysuiReceiver.onDeviceCredentialPressed();
+ mBiometricService.mAuthSession.mSysuiReceiver.onDeviceCredentialPressed();
waitForIdle();
assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FINGERPRINT,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
0 /* vendorCode */);
waitForIdle();
assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
}
@Test
public void testLockout_whileAuthenticating_credentialAllowed() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */,
Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
- assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FINGERPRINT,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
0 /* vendorCode */);
waitForIdle();
assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
verify(mBiometricService.mStatusBarService).onBiometricError(
- eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ eq(TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_LOCKOUT),
eq(0 /* vendorCode */));
}
@Test
public void testLockout_whenAuthenticating_credentialNotAllowed() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
- assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FINGERPRINT,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS,
0 /* vendorCode */);
waitForIdle();
assertEquals(STATE_ERROR_PENDING_SYSUI,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
verify(mBiometricService.mStatusBarService).onBiometricError(
- eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ eq(TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
eq(0 /* vendorCode */));
}
@@ -1033,20 +1031,20 @@ public class BiometricServiceTest {
@Test
public void testDismissedReasonUserCancel_whileAuthenticating_cancelsHalAuthentication()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
- mBiometricService.mSysuiReceiver.onDialogDismissed(
+ mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null /* credentialAttestation */);
waitForIdle();
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ eq(TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
eq(0 /* vendorCode */));
verify(mBiometricService.mSensors.get(0).impl).cancelAuthenticationFromService(
any(), any(), anyLong());
- assertNull(mBiometricService.mCurrentAuthSession);
+ assertNull(mBiometricService.mAuthSession);
}
@Test
@@ -1055,12 +1053,12 @@ public class BiometricServiceTest {
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FACE,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
0 /* vendorCode */);
- mBiometricService.mSysuiReceiver.onDialogDismissed(
+ mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_NEGATIVE, null /* credentialAttestation */);
waitForIdle();
@@ -1069,18 +1067,17 @@ public class BiometricServiceTest {
}
@Test
- public void testDismissedReasonUserCancel_whilePaused_invokesHalCancel() throws
- Exception {
+ public void testDismissedReasonUserCancel_whilePaused_invokesHalCancel() throws Exception {
setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FACE,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
0 /* vendorCode */);
- mBiometricService.mSysuiReceiver.onDialogDismissed(
+ mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null /* credentialAttestation */);
waitForIdle();
@@ -1094,10 +1091,10 @@ public class BiometricServiceTest {
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
true /* requireConfirmation */, null /* authenticators */);
- mBiometricService.mBiometricSensorReceiver.onAuthenticationSucceeded(
+ mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded(
SENSOR_ID_FACE,
new byte[69] /* HAT */);
- mBiometricService.mSysuiReceiver.onDialogDismissed(
+ mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null /* credentialAttestation */);
waitForIdle();
@@ -1108,19 +1105,19 @@ public class BiometricServiceTest {
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
eq(0 /* vendorCode */));
verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
- assertNull(mBiometricService.mCurrentAuthSession);
+ assertNull(mBiometricService.mAuthSession);
}
@Test
public void testAcquire_whenAuthenticating_sentToSystemUI() throws Exception {
when(mContext.getResources().getString(anyInt())).thenReturn("test string");
- final int modality = BiometricAuthenticator.TYPE_FINGERPRINT;
+ final int modality = TYPE_FINGERPRINT;
setupAuthForOnly(modality, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
- mBiometricService.mBiometricSensorReceiver.onAcquired(
+ mBiometricService.mAuthSession.mSensorReceiver.onAcquired(
SENSOR_ID_FINGERPRINT,
FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY,
0 /* vendorCode */);
@@ -1130,29 +1127,29 @@ public class BiometricServiceTest {
// string is retrieved for now, but it's also very unlikely to break anyway.
verify(mBiometricService.mStatusBarService)
.onBiometricHelp(eq(modality), anyString());
- assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
}
@Test
public void testCancel_whenAuthenticating() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
- mBiometricService.mImpl.cancelAuthentication(mBiometricService.mCurrentAuthSession.mToken,
+ mBiometricService.mImpl.cancelAuthentication(mBiometricService.mAuthSession.mToken,
TEST_PACKAGE_NAME, TEST_REQUEST_ID);
waitForIdle();
// Pretend that the HAL has responded to cancel with ERROR_CANCELED
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FINGERPRINT,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
0 /* vendorCode */);
waitForIdle();
// Hides system dialog and invokes the onError callback
- verify(mReceiver1).onError(eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ verify(mReceiver1).onError(eq(TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
eq(0 /* vendorCode */));
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
@@ -1161,7 +1158,7 @@ public class BiometricServiceTest {
@Test
public void testCanAuthenticate_whenDeviceHasRequestedBiometricStrength() throws Exception {
// When only biometric is requested, and sensor is strong enough
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG));
@@ -1170,7 +1167,7 @@ public class BiometricServiceTest {
@Test
public void testCanAuthenticate_whenDeviceDoesNotHaveRequestedBiometricStrength()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
// When only biometric is requested, and sensor is not strong enough
when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
@@ -1208,9 +1205,8 @@ public class BiometricServiceTest {
@Test
public void testCanAuthenticate_whenNoBiometricsEnrolled() throws Exception {
// With credential set up, test the following.
- when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
- .thenReturn(true);
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
false /* enrolled */);
// When only biometric is requested
@@ -1277,7 +1273,7 @@ public class BiometricServiceTest {
private void testCanAuthenticate_whenLockedOut(@LockoutTracker.LockoutMode int lockoutMode)
throws Exception {
// When only biometric is requested, and sensor is strong enough
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
.thenReturn(lockoutMode);
@@ -1311,7 +1307,7 @@ public class BiometricServiceTest {
for (int i = 0; i < testCases.length; i++) {
final BiometricSensor sensor =
new BiometricSensor(mContext, 0 /* id */,
- BiometricAuthenticator.TYPE_FINGERPRINT,
+ TYPE_FINGERPRINT,
testCases[i][0],
mock(IBiometricAuthenticator.class)) {
@Override
@@ -1341,7 +1337,7 @@ public class BiometricServiceTest {
.thenReturn(true);
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
mBiometricService.mImpl.registerAuthenticator(0 /* testId */,
- BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
mFingerprintAuthenticator);
verify(mBiometricService.mBiometricStrengthController).updateStrengths();
@@ -1360,7 +1356,7 @@ public class BiometricServiceTest {
.thenReturn(true);
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
mBiometricService.mImpl.registerAuthenticator(testId /* id */,
- BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
mFingerprintAuthenticator);
// Downgrade the authenticator
@@ -1378,7 +1374,7 @@ public class BiometricServiceTest {
false /* requireConfirmation */, authenticators);
waitForIdle();
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ eq(TYPE_FINGERPRINT),
eq(BiometricPrompt.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED),
eq(0) /* vendorCode */);
@@ -1392,7 +1388,7 @@ public class BiometricServiceTest {
authenticators);
waitForIdle();
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
- eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+ eq(mBiometricService.mAuthSession.mPromptInfo),
any(IBiometricSysuiReceiver.class),
AdditionalMatchers.aryEq(new int[] {testId}),
eq(false) /* credentialAllowed */,
@@ -1414,9 +1410,9 @@ public class BiometricServiceTest {
false /* requireConfirmation */,
authenticators);
waitForIdle();
- assertTrue(Utils.isCredentialRequested(mBiometricService.mCurrentAuthSession.mPromptInfo));
+ assertTrue(Utils.isCredentialRequested(mBiometricService.mAuthSession.mPromptInfo));
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
- eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+ eq(mBiometricService.mAuthSession.mPromptInfo),
any(IBiometricSysuiReceiver.class),
AdditionalMatchers.aryEq(new int[0]) /* sensorIds */,
eq(true) /* credentialAllowed */,
@@ -1442,7 +1438,7 @@ public class BiometricServiceTest {
false /* requireConfirmation */, authenticators);
waitForIdle();
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
- eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+ eq(mBiometricService.mAuthSession.mPromptInfo),
any(IBiometricSysuiReceiver.class),
AdditionalMatchers.aryEq(new int[] {testId}) /* sensorIds */,
eq(false) /* credentialAllowed */,
@@ -1495,29 +1491,29 @@ public class BiometricServiceTest {
@Test
public void testWorkAuthentication_fingerprintWorksIfNotDisabledByDevicePolicyManager()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
when(mDevicePolicyManager
.getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */))
.thenReturn(~DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1,
Authenticators.BIOMETRIC_STRONG);
waitForIdle();
- assertEquals(STATE_AUTH_CALLED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_CALLED, mBiometricService.mAuthSession.getState());
startPendingAuthSession(mBiometricService);
waitForIdle();
- assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
}
@Test
public void testAuthentication_normalAppIgnoresDevicePolicy() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
when(mDevicePolicyManager
.getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */))
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, Authenticators.BIOMETRIC_STRONG);
waitForIdle();
- assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
}
@Test
@@ -1530,18 +1526,17 @@ public class BiometricServiceTest {
invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1,
Authenticators.BIOMETRIC_STRONG);
waitForIdle();
- assertEquals(STATE_AUTH_CALLED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_CALLED, mBiometricService.mAuthSession.getState());
startPendingAuthSession(mBiometricService);
waitForIdle();
- assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
}
@Test
public void testWorkAuthentication_fingerprintFailsIfDisabledByDevicePolicyManager()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
- when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
- .thenReturn(true);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true);
when(mDevicePolicyManager
.getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */))
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
@@ -1555,9 +1550,9 @@ public class BiometricServiceTest {
invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver2,
Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL);
waitForIdle();
- assertNotNull(mBiometricService.mCurrentAuthSession);
+ assertNotNull(mBiometricService.mAuthSession);
assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
verify(mReceiver2, never()).onError(anyInt(), anyInt(), anyInt());
}
@@ -1580,7 +1575,7 @@ public class BiometricServiceTest {
when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
- if ((modality & BiometricAuthenticator.TYPE_FINGERPRINT) != 0) {
+ if ((modality & TYPE_FINGERPRINT) != 0) {
when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
.thenReturn(enrolled);
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
@@ -1614,7 +1609,7 @@ public class BiometricServiceTest {
final int modality = modalities[i];
final int strength = strengths[i];
- if ((modality & BiometricAuthenticator.TYPE_FINGERPRINT) != 0) {
+ if ((modality & TYPE_FINGERPRINT) != 0) {
when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
.thenReturn(true);
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
@@ -1654,8 +1649,9 @@ public class BiometricServiceTest {
startPendingAuthSession(mBiometricService);
waitForIdle();
- assertNotNull(mBiometricService.mCurrentAuthSession);
- assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+ assertNotNull(mBiometricService.mAuthSession);
+ assertEquals(TEST_REQUEST_ID, mBiometricService.mAuthSession.getRequestId());
+ assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
return requestId;
}
@@ -1663,14 +1659,14 @@ public class BiometricServiceTest {
private static void startPendingAuthSession(BiometricService service) throws Exception {
// Get the cookie so we can pretend the hardware is ready to authenticate
// Currently we only support single modality per auth
- final PreAuthInfo preAuthInfo = service.mCurrentAuthSession.mPreAuthInfo;
+ final PreAuthInfo preAuthInfo = service.mAuthSession.mPreAuthInfo;
assertEquals(preAuthInfo.eligibleSensors.size(), 1);
assertEquals(preAuthInfo.numSensorsWaitingForCookie(), 1);
final int cookie = preAuthInfo.eligibleSensors.get(0).getCookie();
assertNotEquals(cookie, 0);
- service.mImpl.onReadyForAuthentication(cookie);
+ service.mImpl.onReadyForAuthentication(TEST_REQUEST_ID, cookie);
}
private static long invokeAuthenticate(IBiometricService.Stub service,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
index bfb0be760f85..f40b31a0bc0d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
@@ -29,12 +29,8 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.withSettings;
-import android.content.Context;
import android.hardware.biometrics.BiometricConstants;
-import android.os.Handler;
-import android.os.Looper;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
@@ -43,9 +39,11 @@ import androidx.test.filters.SmallTest;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import java.util.LinkedList;
@@ -53,39 +51,38 @@ import java.util.LinkedList;
@SmallTest
public class CoexCoordinatorTest {
- private static final String TAG = "CoexCoordinatorTest";
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
- private CoexCoordinator mCoexCoordinator;
- private Handler mHandler;
-
- @Mock
- private Context mContext;
@Mock
private CoexCoordinator.Callback mCallback;
@Mock
private CoexCoordinator.ErrorCallback mErrorCallback;
+ @Mock
+ private AuthenticationClient mFaceClient;
+ @Mock
+ private AuthenticationClient mFingerprintClient;
+ @Mock(extraInterfaces = {Udfps.class})
+ private AuthenticationClient mUdfpsClient;
+
+ private CoexCoordinator mCoexCoordinator;
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mHandler = new Handler(Looper.getMainLooper());
-
mCoexCoordinator = CoexCoordinator.getInstance();
mCoexCoordinator.setAdvancedLogicEnabled(true);
mCoexCoordinator.setFaceHapticDisabledWhenNonBypass(true);
+ mCoexCoordinator.reset();
}
@Test
public void testBiometricPrompt_authSuccess() {
- mCoexCoordinator.reset();
-
- AuthenticationClient<?> client = mock(AuthenticationClient.class);
- when(client.isBiometricPrompt()).thenReturn(true);
+ when(mFaceClient.isBiometricPrompt()).thenReturn(true);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
- mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, client, mCallback);
+ mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
+ mFaceClient, mCallback);
verify(mCallback).sendHapticFeedback();
verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
verify(mCallback).handleLifecycleAfterAuth();
@@ -93,15 +90,12 @@ public class CoexCoordinatorTest {
@Test
public void testBiometricPrompt_authReject_whenNotLockedOut() {
- mCoexCoordinator.reset();
-
- AuthenticationClient<?> client = mock(AuthenticationClient.class);
- when(client.isBiometricPrompt()).thenReturn(true);
+ when(mFaceClient.isBiometricPrompt()).thenReturn(true);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
- client, LockoutTracker.LOCKOUT_NONE, mCallback);
+ mFaceClient, LockoutTracker.LOCKOUT_NONE, mCallback);
verify(mCallback).sendHapticFeedback();
verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
verify(mCallback).handleLifecycleAfterAuth();
@@ -109,30 +103,97 @@ public class CoexCoordinatorTest {
@Test
public void testBiometricPrompt_authReject_whenLockedOut() {
- mCoexCoordinator.reset();
+ when(mFaceClient.isBiometricPrompt()).thenReturn(true);
- AuthenticationClient<?> client = mock(AuthenticationClient.class);
- when(client.isBiometricPrompt()).thenReturn(true);
-
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
- client, LockoutTracker.LOCKOUT_TIMED, mCallback);
+ mFaceClient, LockoutTracker.LOCKOUT_TIMED, mCallback);
verify(mCallback).sendHapticFeedback();
verify(mCallback, never()).sendAuthenticationResult(anyBoolean());
verify(mCallback).handleLifecycleAfterAuth();
}
@Test
- public void testKeyguard_faceAuthOnly_success() {
- mCoexCoordinator.reset();
+ public void testBiometricPrompt_coex_success() {
+ testBiometricPrompt_coex_success(false /* twice */);
+ }
+
+ @Test
+ public void testBiometricPrompt_coex_successWithoutDouble() {
+ testBiometricPrompt_coex_success(true /* twice */);
+ }
+
+ private void testBiometricPrompt_coex_success(boolean twice) {
+ initFaceAndFingerprintForBiometricPrompt();
+ when(mFaceClient.wasAuthSuccessful()).thenReturn(true);
+ when(mUdfpsClient.wasAuthSuccessful()).thenReturn(twice, true);
+
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
+
+ mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
+ mFaceClient, mCallback);
+ mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
+ mUdfpsClient, mCallback);
+
+ if (twice) {
+ verify(mCallback, never()).sendHapticFeedback();
+ } else {
+ verify(mCallback).sendHapticFeedback();
+ }
+ }
+
+ @Test
+ public void testBiometricPrompt_coex_reject() {
+ initFaceAndFingerprintForBiometricPrompt();
+
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
+
+ mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
+ mFaceClient, LockoutTracker.LOCKOUT_NONE, mCallback);
+
+ verify(mCallback, never()).sendHapticFeedback();
+
+ mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
+ mUdfpsClient, LockoutTracker.LOCKOUT_NONE, mCallback);
+
+ verify(mCallback).sendHapticFeedback();
+ }
+
+ @Test
+ public void testBiometricPrompt_coex_errorNoHaptics() {
+ initFaceAndFingerprintForBiometricPrompt();
+
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
+
+ mCoexCoordinator.onAuthenticationError(mFaceClient,
+ BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
+ mCoexCoordinator.onAuthenticationError(mUdfpsClient,
+ BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
+
+ verify(mErrorCallback, never()).sendHapticFeedback();
+ }
- AuthenticationClient<?> client = mock(AuthenticationClient.class);
- when(client.isKeyguard()).thenReturn(true);
+ private void initFaceAndFingerprintForBiometricPrompt() {
+ when(mFaceClient.isKeyguard()).thenReturn(false);
+ when(mFaceClient.isBiometricPrompt()).thenReturn(true);
+ when(mFaceClient.wasAuthAttempted()).thenReturn(true);
+ when(mUdfpsClient.isKeyguard()).thenReturn(false);
+ when(mUdfpsClient.isBiometricPrompt()).thenReturn(true);
+ when(mUdfpsClient.wasAuthAttempted()).thenReturn(true);
+ }
+
+ @Test
+ public void testKeyguard_faceAuthOnly_success() {
+ when(mFaceClient.isKeyguard()).thenReturn(true);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
- mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, client, mCallback);
+ mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
+ mFaceClient, mCallback);
verify(mCallback).sendHapticFeedback();
verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
verify(mCallback).handleLifecycleAfterAuth();
@@ -140,21 +201,16 @@ public class CoexCoordinatorTest {
@Test
public void testKeyguard_faceAuth_udfpsNotTouching_faceSuccess() {
- mCoexCoordinator.reset();
-
- AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
- when(faceClient.isKeyguard()).thenReturn(true);
+ when(mFaceClient.isKeyguard()).thenReturn(true);
- AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
- withSettings().extraInterfaces(Udfps.class));
- when(udfpsClient.isKeyguard()).thenReturn(true);
- when(((Udfps) udfpsClient).isPointerDown()).thenReturn(false);
+ when(mUdfpsClient.isKeyguard()).thenReturn(true);
+ when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(false);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
- mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient,
- mCallback);
+ mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
+ mFaceClient, mCallback);
// Haptics tested in #testKeyguard_bypass_haptics. Let's leave this commented out (instead
// of removed) to keep this context.
// verify(mCallback).sendHapticFeedback();
@@ -192,25 +248,19 @@ public class CoexCoordinatorTest {
private void testKeyguard_bypass_haptics(boolean bypassEnabled, boolean faceAccepted,
boolean shouldReceiveHaptics) {
- mCoexCoordinator.reset();
-
- AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
- when(faceClient.isKeyguard()).thenReturn(true);
- when(faceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
+ when(mFaceClient.isKeyguard()).thenReturn(true);
+ when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
+ when(mUdfpsClient.isKeyguard()).thenReturn(true);
+ when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(false);
- AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
- withSettings().extraInterfaces(Udfps.class));
- when(udfpsClient.isKeyguard()).thenReturn(true);
- when(((Udfps) udfpsClient).isPointerDown()).thenReturn(false);
-
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
if (faceAccepted) {
- mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient,
+ mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mFaceClient,
mCallback);
} else {
- mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, faceClient,
+ mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient,
LockoutTracker.LOCKOUT_NONE, mCallback);
}
@@ -244,24 +294,18 @@ public class CoexCoordinatorTest {
private void testKeyguard_faceAuth_udfpsTouching_faceSuccess(boolean thenUdfpsAccepted,
long udfpsRejectedAfterMs) {
- mCoexCoordinator.reset();
-
- AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
- when(faceClient.isKeyguard()).thenReturn(true);
+ when(mFaceClient.isKeyguard()).thenReturn(true);
+ when(mUdfpsClient.isKeyguard()).thenReturn(true);
+ when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true);
+ when(mUdfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
- AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
- withSettings().extraInterfaces(Udfps.class));
- when(udfpsClient.isKeyguard()).thenReturn(true);
- when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true);
- when (udfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
-
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
// For easier reading
final CoexCoordinator.Callback faceCallback = mCallback;
- mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient,
+ mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mFaceClient,
faceCallback);
verify(faceCallback, never()).sendHapticFeedback();
verify(faceCallback, never()).sendAuthenticationResult(anyBoolean());
@@ -272,9 +316,9 @@ public class CoexCoordinatorTest {
// Reset the mock
CoexCoordinator.Callback udfpsCallback = mock(CoexCoordinator.Callback.class);
assertEquals(1, mCoexCoordinator.mSuccessfulAuths.size());
- assertEquals(faceClient, mCoexCoordinator.mSuccessfulAuths.get(0).mAuthenticationClient);
+ assertEquals(mFaceClient, mCoexCoordinator.mSuccessfulAuths.get(0).mAuthenticationClient);
if (thenUdfpsAccepted) {
- mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, udfpsClient,
+ mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mUdfpsClient,
udfpsCallback);
verify(udfpsCallback).sendHapticFeedback();
verify(udfpsCallback).sendAuthenticationResult(true /* addAuthTokenIfStrong */);
@@ -284,7 +328,7 @@ public class CoexCoordinatorTest {
assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty());
} else {
- mCoexCoordinator.onAuthenticationRejected(udfpsRejectedAfterMs, udfpsClient,
+ mCoexCoordinator.onAuthenticationRejected(udfpsRejectedAfterMs, mUdfpsClient,
LockoutTracker.LOCKOUT_NONE, udfpsCallback);
if (udfpsRejectedAfterMs <= CoexCoordinator.SUCCESSFUL_AUTH_VALID_DURATION_MS) {
verify(udfpsCallback, never()).sendHapticFeedback();
@@ -310,56 +354,44 @@ public class CoexCoordinatorTest {
@Test
public void testKeyguard_udfpsAuthSuccess_whileFaceScanning() {
- mCoexCoordinator.reset();
-
- AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
- when(faceClient.isKeyguard()).thenReturn(true);
- when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ when(mFaceClient.isKeyguard()).thenReturn(true);
+ when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ when(mUdfpsClient.isKeyguard()).thenReturn(true);
+ when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true);
- AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
- withSettings().extraInterfaces(Udfps.class));
- when(udfpsClient.isKeyguard()).thenReturn(true);
- when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
-
- mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, udfpsClient,
+ mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mUdfpsClient,
mCallback);
verify(mCallback).sendHapticFeedback();
verify(mCallback).sendAuthenticationResult(eq(true));
- verify(faceClient).cancel();
+ verify(mFaceClient).cancel();
verify(mCallback).handleLifecycleAfterAuth();
}
@Test
public void testKeyguard_faceRejectedWhenUdfpsTouching_thenUdfpsRejected() {
- mCoexCoordinator.reset();
-
- AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
- when(faceClient.isKeyguard()).thenReturn(true);
- when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ when(mFaceClient.isKeyguard()).thenReturn(true);
+ when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ when(mUdfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ when(mUdfpsClient.isKeyguard()).thenReturn(true);
+ when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true);
- AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
- withSettings().extraInterfaces(Udfps.class));
- when(udfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
- when(udfpsClient.isKeyguard()).thenReturn(true);
- when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
-
- mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, faceClient,
+ mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient,
LockoutTracker.LOCKOUT_NONE, mCallback);
verify(mCallback, never()).sendHapticFeedback();
verify(mCallback).handleLifecycleAfterAuth();
// BiometricScheduler removes the face authentication client after rejection
- mCoexCoordinator.removeAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
+ mCoexCoordinator.removeAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
// Then UDFPS rejected
CoexCoordinator.Callback udfpsCallback = mock(CoexCoordinator.Callback.class);
- mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, udfpsClient,
+ mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, mUdfpsClient,
LockoutTracker.LOCKOUT_NONE, udfpsCallback);
verify(udfpsCallback).sendHapticFeedback();
verify(udfpsCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
@@ -368,26 +400,20 @@ public class CoexCoordinatorTest {
@Test
public void testKeyguard_udfpsRejected_thenFaceRejected_noKeyguardBypass() {
- mCoexCoordinator.reset();
-
- AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
- when(faceClient.isKeyguard()).thenReturn(true);
- when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
- when(faceClient.isKeyguardBypassEnabled()).thenReturn(false); // TODO: also test "true" case
+ when(mFaceClient.isKeyguard()).thenReturn(true);
+ when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(false); // TODO: also test "true" case
+ when(mUdfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ when(mUdfpsClient.isKeyguard()).thenReturn(true);
+ when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true);
- AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
- withSettings().extraInterfaces(Udfps.class));
- when(udfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
- when(udfpsClient.isKeyguard()).thenReturn(true);
- when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
-
- mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, udfpsClient,
- LockoutTracker.LOCKOUT_NONE, mCallback);
+ mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
+ mUdfpsClient, LockoutTracker.LOCKOUT_NONE, mCallback);
// Auth was attempted
- when(udfpsClient.getState())
+ when(mUdfpsClient.getState())
.thenReturn(AuthenticationClient.STATE_STARTED_PAUSED_ATTEMPTED);
verify(mCallback, never()).sendHapticFeedback();
verify(mCallback).handleLifecycleAfterAuth();
@@ -395,7 +421,7 @@ public class CoexCoordinatorTest {
// Then face rejected. Note that scheduler leaves UDFPS in the CoexCoordinator since
// unlike face, its lifecycle becomes "paused" instead of "finished".
CoexCoordinator.Callback faceCallback = mock(CoexCoordinator.Callback.class);
- mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, faceClient,
+ mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, mFaceClient,
LockoutTracker.LOCKOUT_NONE, faceCallback);
verify(faceCallback).sendHapticFeedback();
verify(faceCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
@@ -404,20 +430,16 @@ public class CoexCoordinatorTest {
@Test
public void testKeyguard_capacitiveAccepted_whenFaceScanning() {
- mCoexCoordinator.reset();
-
- AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
- when(faceClient.isKeyguard()).thenReturn(true);
- when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
-
- AuthenticationClient<?> fpClient = mock(AuthenticationClient.class);
- when(fpClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
- when(fpClient.isKeyguard()).thenReturn(true);
+ when(mFaceClient.isKeyguard()).thenReturn(true);
+ when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ when(mFingerprintClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ when(mFingerprintClient.isKeyguard()).thenReturn(true);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FP_OTHER, fpClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FP_OTHER, mFingerprintClient);
- mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, fpClient, mCallback);
+ mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
+ mFingerprintClient, mCallback);
verify(mCallback).sendHapticFeedback();
verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
verify(mCallback).handleLifecycleAfterAuth();
@@ -425,21 +447,16 @@ public class CoexCoordinatorTest {
@Test
public void testKeyguard_capacitiveRejected_whenFaceScanning() {
- mCoexCoordinator.reset();
-
- AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
- when(faceClient.isKeyguard()).thenReturn(true);
- when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
-
- AuthenticationClient<?> fpClient = mock(AuthenticationClient.class);
- when(fpClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
- when(fpClient.isKeyguard()).thenReturn(true);
+ when(mFaceClient.isKeyguard()).thenReturn(true);
+ when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ when(mFingerprintClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ when(mFingerprintClient.isKeyguard()).thenReturn(true);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FP_OTHER, fpClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FP_OTHER, mFingerprintClient);
- mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, fpClient,
- LockoutTracker.LOCKOUT_NONE, mCallback);
+ mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
+ mFingerprintClient, LockoutTracker.LOCKOUT_NONE, mCallback);
verify(mCallback).sendHapticFeedback();
verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
verify(mCallback).handleLifecycleAfterAuth();
@@ -447,14 +464,11 @@ public class CoexCoordinatorTest {
@Test
public void testNonKeyguard_rejectAndNotLockedOut() {
- mCoexCoordinator.reset();
+ when(mFaceClient.isKeyguard()).thenReturn(false);
+ when(mFaceClient.isBiometricPrompt()).thenReturn(true);
- AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
- when(faceClient.isKeyguard()).thenReturn(false);
- when(faceClient.isBiometricPrompt()).thenReturn(true);
-
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
- mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, faceClient,
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient,
LockoutTracker.LOCKOUT_NONE, mCallback);
verify(mCallback).sendHapticFeedback();
@@ -464,14 +478,11 @@ public class CoexCoordinatorTest {
@Test
public void testNonKeyguard_rejectLockedOut() {
- mCoexCoordinator.reset();
-
- AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
- when(faceClient.isKeyguard()).thenReturn(false);
- when(faceClient.isBiometricPrompt()).thenReturn(true);
+ when(mFaceClient.isKeyguard()).thenReturn(false);
+ when(mFaceClient.isBiometricPrompt()).thenReturn(true);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
- mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, faceClient,
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient,
LockoutTracker.LOCKOUT_TIMED, mCallback);
verify(mCallback).sendHapticFeedback();
@@ -496,16 +507,13 @@ public class CoexCoordinatorTest {
@Test
public void testBiometricPrompt_FaceError() {
- mCoexCoordinator.reset();
-
- AuthenticationClient<?> client = mock(AuthenticationClient.class);
- when(client.isBiometricPrompt()).thenReturn(true);
- when(client.wasAuthAttempted()).thenReturn(true);
+ when(mFaceClient.isBiometricPrompt()).thenReturn(true);
+ when(mFaceClient.wasAuthAttempted()).thenReturn(true);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
- mCoexCoordinator.onAuthenticationError(client, BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
- mErrorCallback);
+ mCoexCoordinator.onAuthenticationError(mFaceClient,
+ BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
verify(mErrorCallback).sendHapticFeedback();
}
@@ -520,18 +528,15 @@ public class CoexCoordinatorTest {
}
private void testKeyguard_faceAuthOnly(boolean bypassEnabled) {
- mCoexCoordinator.reset();
-
- AuthenticationClient<?> client = mock(AuthenticationClient.class);
- when(client.isKeyguard()).thenReturn(true);
- when(client.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
- when(client.wasAuthAttempted()).thenReturn(true);
- when(client.wasUserDetected()).thenReturn(true);
+ when(mFaceClient.isKeyguard()).thenReturn(true);
+ when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
+ when(mFaceClient.wasAuthAttempted()).thenReturn(true);
+ when(mFaceClient.wasUserDetected()).thenReturn(true);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
- mCoexCoordinator.onAuthenticationError(client, BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
- mErrorCallback);
+ mCoexCoordinator.onAuthenticationError(mFaceClient,
+ BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
verify(mErrorCallback).sendHapticFeedback();
}
@@ -546,23 +551,17 @@ public class CoexCoordinatorTest {
}
private void testKeyguard_coex_faceError(boolean bypassEnabled) {
- mCoexCoordinator.reset();
-
- AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
- when(faceClient.isKeyguard()).thenReturn(true);
- when(faceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
- when(faceClient.wasAuthAttempted()).thenReturn(true);
- when(faceClient.wasUserDetected()).thenReturn(true);
-
- AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
- withSettings().extraInterfaces(Udfps.class));
- when(udfpsClient.isKeyguard()).thenReturn(true);
- when(((Udfps) udfpsClient).isPointerDown()).thenReturn(false);
+ when(mFaceClient.isKeyguard()).thenReturn(true);
+ when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
+ when(mFaceClient.wasAuthAttempted()).thenReturn(true);
+ when(mFaceClient.wasUserDetected()).thenReturn(true);
+ when(mUdfpsClient.isKeyguard()).thenReturn(true);
+ when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(false);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
- mCoexCoordinator.onAuthenticationError(faceClient,
+ mCoexCoordinator.onAuthenticationError(mFaceClient,
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
if (bypassEnabled) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java
new file mode 100644
index 000000000000..76a5accc5fe9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.server.biometrics.sensors.face.aidl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.face.ISession;
+import android.hardware.face.Face;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+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.HashMap;
+import java.util.Map;
+
+@Presubmit
+@SmallTest
+public class FaceRemovalClientTest {
+
+ private static final int USER_ID = 12;
+
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private ISession mHal;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Sensor.HalSessionCallback mHalSessionCallback;
+ @Mock
+ private BiometricUtils<Face> mUtils;
+ @Mock
+ private BiometricAuthenticator.Identifier mIdentifier;
+ private Map<Integer, Long> mAuthenticatorIds = new HashMap<Integer, Long>();
+
+ @Before
+ public void setup() {
+ when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+ i -> i.getArgument(0));
+ }
+
+ @Test
+ public void testFaceRemovalClient() throws RemoteException {
+ final int authenticatorId = 1;
+ int[] authenticatorIds = new int[]{authenticatorId};
+ final FaceRemovalClient client = createClient(1, authenticatorIds);
+ when(mIdentifier.getBiometricId()).thenReturn(authenticatorId);
+ client.start(mCallback);
+ verify(mHal).removeEnrollments(authenticatorIds);
+ client.onRemoved(mIdentifier, 0 /* remaining */);
+ verify(mClientMonitorCallbackConverter).onRemoved(
+ eq(mIdentifier) /* identifier */, eq(0) /* remaining */);
+ verify(mCallback).onClientFinished(client, true);
+ }
+
+ @Test
+ public void clientSendsErrorWhenHALFailsToRemoveEnrollment() throws RemoteException {
+ final FaceRemovalClient client = createClient(1, new int[0]);
+ client.start(mCallback);
+ client.onRemoved(null, 0 /* remaining */);
+ verify(mClientMonitorCallbackConverter).onError(eq(5) /* sensorId */, anyInt(),
+ eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_REMOVE), eq(0) /* vendorCode*/);
+ verify(mCallback).onClientFinished(client, false);
+ }
+
+ private FaceRemovalClient createClient(int version, int[] biometricIds) throws RemoteException {
+ when(mHal.getInterfaceVersion()).thenReturn(version);
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ return new FaceRemovalClient(mContext, () -> aidl, mToken,
+ mClientMonitorCallbackConverter, biometricIds, USER_ID,
+ "own-it", mUtils /* utils */, 5 /* sensorId */, mBiometricLogger, mBiometricContext,
+ mAuthenticatorIds);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java
index acb20edfe8d8..6de7fddf6ccd 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java
@@ -229,8 +229,8 @@ public class LockSettingsStrongAuthTest {
}
private void verifyAlarm(long when, String tag, AlarmManager.OnAlarmListener alarm) {
- verify(mAlarmManager).set(
- eq(AlarmManager.ELAPSED_REALTIME),
+ verify(mAlarmManager).setExact(
+ eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
eq(when),
eq(tag),
eq(alarm),
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 16c5bfec76ae..6fe2d337e4a0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1085,7 +1085,7 @@ public class ActivityStarterTests extends WindowTestsBase {
starter.setActivityOptions(options.toBundle())
.setReason("testWindowingModeOptionsLaunchAdjacent")
.setOutActivity(outActivity).execute();
- assertThat(outActivity[0].inSplitScreenWindowingMode()).isFalse();
+ assertThat(outActivity[0].inMultiWindowMode()).isFalse();
// Move activity to split-screen-primary stack and make sure it has the focus.
TestSplitOrganizer splitOrg = new TestSplitOrganizer(mAtm, top.getDisplayContent());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index c08387024818..925f4f5a4130 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1914,7 +1914,7 @@ public class DisplayContentTests extends WindowTestsBase {
final WindowState nextImeTargetApp = createWindow(null /* parent */,
TYPE_BASE_APPLICATION, "nextImeTargetApp");
spyOn(child1);
- doReturn(true).when(child1).inSplitScreenWindowingMode();
+ doReturn(false).when(mDisplayContent).shouldImeAttachedToApp();
mDisplayContent.setImeLayeringTarget(child1);
spyOn(nextImeTargetApp);
@@ -2418,10 +2418,10 @@ public class DisplayContentTests extends WindowTestsBase {
public void testKeepClearAreasMultipleWindows() {
final WindowState w1 = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent, "w1");
final Rect rect1 = new Rect(0, 0, 10, 10);
- w1.setKeepClearAreas(Arrays.asList(rect1));
+ w1.setKeepClearAreas(Arrays.asList(rect1), Collections.emptyList());
final WindowState w2 = createWindow(null, TYPE_NOTIFICATION_SHADE, mDisplayContent, "w2");
final Rect rect2 = new Rect(10, 10, 20, 20);
- w2.setKeepClearAreas(Arrays.asList(rect2));
+ w2.setKeepClearAreas(Arrays.asList(rect2), Collections.emptyList());
// No keep clear areas on display, because the windows are not visible
assertEquals(Arrays.asList(), mDisplayContent.getKeepClearAreas());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 3c3351c0b207..021568dd97b4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -451,7 +451,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase {
final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
final int topOrientation = DisplayContentTests.getRotatedOrientation(mDefaultDisplay);
assertFalse(displayRotation.updateOrientation(topOrientation, false /* forceUpdate */));
- assertEquals(recents.mOrientation, displayRotation.getLastOrientation());
+ assertEquals(ActivityInfo.SCREEN_ORIENTATION_UNSET, displayRotation.getLastOrientation());
final int prevRotation = mDisplayContent.getRotation();
mWm.cleanupRecentsAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 501f0c4ac1b3..8474c3829827 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
@@ -691,7 +692,7 @@ public class TransitionTests extends WindowTestsBase {
anyInt() /* orientation */, anyInt() /* lastRotation */);
// Rotation update is skipped while the recents animation is running.
assertFalse(mDisplayContent.updateRotationUnchecked());
- assertEquals(SCREEN_ORIENTATION_NOSENSOR, displayRotation.getLastOrientation());
+ assertEquals(SCREEN_ORIENTATION_UNSET, displayRotation.getLastOrientation());
// Return to the app without fixed orientation from recents.
app.moveFocusableActivityToTop("test");
player.finish();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index ef600f085e50..a554fab76c2b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -97,7 +97,9 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@@ -992,14 +994,18 @@ public class WindowStateTests extends WindowTestsBase {
final Rect keepClearArea1 = new Rect(0, 0, 10, 10);
final Rect keepClearArea2 = new Rect(5, 10, 15, 20);
final List<Rect> keepClearAreas = Arrays.asList(keepClearArea1, keepClearArea2);
- window.setKeepClearAreas(keepClearAreas);
+ window.setKeepClearAreas(keepClearAreas, Collections.emptyList());
// Test that the keep-clear rects are stored and returned
- assertEquals(new ArraySet(keepClearAreas), new ArraySet(window.getKeepClearAreas()));
+ final List<Rect> windowKeepClearAreas = new ArrayList();
+ window.getKeepClearAreas(windowKeepClearAreas, new ArrayList());
+ assertEquals(new ArraySet(keepClearAreas), new ArraySet(windowKeepClearAreas));
// Test that keep-clear rects are overwritten
- window.setKeepClearAreas(Arrays.asList());
- assertEquals(0, window.getKeepClearAreas().size());
+ window.setKeepClearAreas(Collections.emptyList(), Collections.emptyList());
+ windowKeepClearAreas.clear();
+ window.getKeepClearAreas(windowKeepClearAreas, new ArrayList());
+ assertEquals(0, windowKeepClearAreas.size());
// Move the window position
final SurfaceControl.Transaction t = spy(StubTransaction.class);
@@ -1010,13 +1016,60 @@ public class WindowStateTests extends WindowTestsBase {
assertEquals(new Point(frame.left, frame.top), window.mLastSurfacePosition);
// Test that the returned keep-clear rects are translated to display space
- window.setKeepClearAreas(keepClearAreas);
+ window.setKeepClearAreas(keepClearAreas, Collections.emptyList());
Rect expectedArea1 = new Rect(keepClearArea1);
expectedArea1.offset(frame.left, frame.top);
Rect expectedArea2 = new Rect(keepClearArea2);
expectedArea2.offset(frame.left, frame.top);
+ windowKeepClearAreas.clear();
+ window.getKeepClearAreas(windowKeepClearAreas, new ArrayList());
assertEquals(new ArraySet(Arrays.asList(expectedArea1, expectedArea2)),
- new ArraySet(window.getKeepClearAreas()));
+ new ArraySet(windowKeepClearAreas));
+ }
+
+ @Test
+ public void testUnrestrictedKeepClearAreas() {
+ final WindowState window = createWindow(null, TYPE_APPLICATION, "window");
+ makeWindowVisible(window);
+
+ final Rect keepClearArea1 = new Rect(0, 0, 10, 10);
+ final Rect keepClearArea2 = new Rect(5, 10, 15, 20);
+ final List<Rect> keepClearAreas = Arrays.asList(keepClearArea1, keepClearArea2);
+ window.setKeepClearAreas(Collections.emptyList(), keepClearAreas);
+
+ // Test that the keep-clear rects are stored and returned
+ final List<Rect> restrictedKeepClearAreas = new ArrayList();
+ final List<Rect> unrestrictedKeepClearAreas = new ArrayList();
+ window.getKeepClearAreas(restrictedKeepClearAreas, unrestrictedKeepClearAreas);
+ assertEquals(Collections.emptySet(), new ArraySet(restrictedKeepClearAreas));
+ assertEquals(new ArraySet(keepClearAreas), new ArraySet(unrestrictedKeepClearAreas));
+
+ // Test that keep-clear rects are overwritten
+ window.setKeepClearAreas(Collections.emptyList(), Collections.emptyList());
+ unrestrictedKeepClearAreas.clear();
+ window.getKeepClearAreas(unrestrictedKeepClearAreas, new ArrayList());
+ assertEquals(0, unrestrictedKeepClearAreas.size());
+
+ // Move the window position
+ final SurfaceControl.Transaction t = spy(StubTransaction.class);
+ window.mSurfaceControl = mock(SurfaceControl.class);
+ final Rect frame = window.getFrame();
+ frame.set(10, 20, 60, 80);
+ window.updateSurfacePosition(t);
+ assertEquals(new Point(frame.left, frame.top), window.mLastSurfacePosition);
+
+ // Test that the returned keep-clear rects are translated to display space
+ window.setKeepClearAreas(Collections.emptyList(), keepClearAreas);
+ Rect expectedArea1 = new Rect(keepClearArea1);
+ expectedArea1.offset(frame.left, frame.top);
+ Rect expectedArea2 = new Rect(keepClearArea2);
+ expectedArea2.offset(frame.left, frame.top);
+
+ unrestrictedKeepClearAreas.clear();
+ window.getKeepClearAreas(restrictedKeepClearAreas, unrestrictedKeepClearAreas);
+ assertEquals(Collections.emptySet(), new ArraySet(restrictedKeepClearAreas));
+ assertEquals(new ArraySet(Arrays.asList(expectedArea1, expectedArea2)),
+ new ArraySet(unrestrictedKeepClearAreas));
}
}
diff --git a/test-runner/src/android/test/IsolatedContext.java b/test-runner/src/android/test/IsolatedContext.java
index dd4a9a3a4d69..d5f92a3181b9 100644
--- a/test-runner/src/android/test/IsolatedContext.java
+++ b/test-runner/src/android/test/IsolatedContext.java
@@ -17,6 +17,7 @@
package android.test;
import android.accounts.AccountManager;
+import android.content.AttributionSource;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -26,6 +27,7 @@ import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.net.Uri;
+import android.os.Process;
import android.test.mock.MockAccountManager;
import java.io.File;
@@ -64,6 +66,15 @@ public class IsolatedContext extends ContextWrapper {
}
@Override
+ public AttributionSource getAttributionSource() {
+ AttributionSource attributionSource = super.getAttributionSource();
+ if (attributionSource == null) {
+ return new AttributionSource.Builder(Process.myUid()).build();
+ }
+ return attributionSource;
+ }
+
+ @Override
public ContentResolver getContentResolver() {
// We need to return the real resolver so that MailEngine.makeRight can get to the
// subscribed feeds provider. TODO: mock out subscribed feeds too.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index c89e6a44ab6c..48b8779db112 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -97,6 +97,7 @@ open class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestPa
transitions {
taplInstrumentation.launchedAppState.quickSwitchToPreviousApp()
wmHelper.waitForFullScreenApp(testApp1.component)
+ wmHelper.waitSnapshotGone()
wmHelper.waitForAppTransitionIdle()
wmHelper.waitForNavBarStatusBarVisible()
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 5d172e2c1b14..d6c8f4601437 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -51,7 +51,7 @@ import org.junit.runners.Parameterized
/**
* Test quick switching back to previous app from last opened app
*
- * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest`
+ * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsForwardTest`
*
* Actions:
* Launch an app [testApp1]
@@ -101,6 +101,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTes
taplInstrumentation.launchedAppState.quickSwitchToPreviousAppSwipeLeft()
wmHelper.waitForFullScreenApp(testApp2.component)
+ wmHelper.waitSnapshotGone()
wmHelper.waitForAppTransitionIdle()
wmHelper.waitForNavBarStatusBarVisible()
}
diff --git a/tests/TrustTests/Android.bp b/tests/TrustTests/Android.bp
new file mode 100644
index 000000000000..c9c6c5cf193b
--- /dev/null
+++ b/tests/TrustTests/Android.bp
@@ -0,0 +1,39 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "TrustTests",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "androidx.test.uiautomator",
+ "truth-prebuilt",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ test_suites: [
+ "device-tests",
+ ],
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/tests/TrustTests/AndroidManifest.xml b/tests/TrustTests/AndroidManifest.xml
new file mode 100644
index 000000000000..c94152da2bf6
--- /dev/null
+++ b/tests/TrustTests/AndroidManifest.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.trust.test"
+ android:targetSandboxVersion="2">
+
+ <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
+ <uses-permission android:name="android.permission.BIND_DEVICE_ADMIN" />
+ <uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
+ <uses-permission android:name="android.permission.DEVICE_POWER" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT" />
+ <uses-permission android:name="android.permission.TRUST_LISTENER" />
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ <activity android:name="android.trust.TrustTestActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <service
+ android:name=".UserUnlockRequestTrustAgent"
+ android:exported="true"
+ android:label="Test Agent"
+ android:permission="android.permission.BIND_TRUST_AGENT">
+ <intent-filter>
+ <action android:name="android.service.trust.TrustAgentService" />
+ </intent-filter>
+ </service>
+
+ <service
+ android:name=".LockUserTrustAgent"
+ android:exported="true"
+ android:label="Test Agent"
+ android:permission="android.permission.BIND_TRUST_AGENT">
+ <intent-filter>
+ <action android:name="android.service.trust.TrustAgentService" />
+ </intent-filter>
+ </service>
+
+ <service
+ android:name=".GrantAndRevokeTrustAgent"
+ android:exported="true"
+ android:label="Test Agent"
+ android:permission="android.permission.BIND_TRUST_AGENT">
+ <intent-filter>
+ <action android:name="android.service.trust.TrustAgentService" />
+ </intent-filter>
+ </service>
+ </application>
+
+ <!-- self-instrumenting test package. -->
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.trust.test">
+ </instrumentation>
+</manifest>
diff --git a/tests/TrustTests/AndroidTest.xml b/tests/TrustTests/AndroidTest.xml
new file mode 100644
index 000000000000..61b711eb7273
--- /dev/null
+++ b/tests/TrustTests/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<configuration description="TrustTests configuration">
+ <option name="test-tag" value="TrustTests" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="TrustTests.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.trust.test" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false" />
+ </test>
+</configuration>
diff --git a/tests/TrustTests/README.md b/tests/TrustTests/README.md
new file mode 100644
index 000000000000..3427e30536d9
--- /dev/null
+++ b/tests/TrustTests/README.md
@@ -0,0 +1,40 @@
+# TrustTests framework tests
+
+These tests test the "trust" part of the platform primarily implemented via TrustManagerService in
+the system server and TrustAgentService in system apps.
+
+Tests are separated into separate files based on major groupings. When creating new tests, find a
+_closely_ matching existing test file or create a new test file. Prefer many test files over large
+test files.
+
+Each test file has its own trust agent. To create a new trust agent:
+
+1. Create a new class extending from `BaseTrustAgentService` class in your test file
+2. Add a new `<service>` stanza to `AndroidManifest.xml` in this directory for the new agent
+ following the pattern fo the existing agents.
+
+To run:
+
+```atest TrustTests```
+
+## Testing approach:
+
+1. Test the agent service as a black box; avoid inspecting internal state of the service or
+ modifying the system code outside of this directory.
+2. The primary interface to the system is through these three points:
+ 1. `TrustAgentService`, your agent created by the `TrustAgentRule` and accessible via
+ the `agent` property of the rule.
+ 1. Call command methods (e.g. `grantTrust`) directly on the agent
+ 2. Listen to events (e.g. `onUserRequestedUnlock`) by implementing the method in
+ your test's agent class and tracking invocations. See `UserUnlockRequestTest` for an
+ example.
+ 2. `TrustManager` which is the interface the rest of the system (e.g. SystemUI) has to the
+ service.
+ 1. Through this API, simulate system events that the service cares about
+ (e.g. `reportUnlockAttempt`).
+ 3. `TrustListener` which is the interface the rest of the system (e.g. SystemUI) uses to receive
+ events from the service.
+ 1. Through this, verify behavior that affects the rest of the system. For example,
+ see `LockStateTrackingRule`.
+3. To re-use code between tests, prefer creating new rules alongside the existing rules or adding
+ functionality to a _closely_ matching existing rule.
diff --git a/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt b/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt
new file mode 100644
index 000000000000..493f3bd22d2b
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt
@@ -0,0 +1,47 @@
+/*
+ * 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 android.trust
+
+import android.service.trust.TrustAgentService
+import android.util.Log
+import kotlin.reflect.KClass
+
+/**
+ * Base class for test trust agents.
+ */
+abstract class BaseTrustAgentService : TrustAgentService() {
+
+ override fun onCreate() {
+ super.onCreate()
+ Log.d(TAG, "${this::class.simpleName} created")
+ instances[this::class] = this
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ instances.remove(this::class)
+ }
+
+ companion object {
+ private val instances =
+ mutableMapOf<KClass<out BaseTrustAgentService>, BaseTrustAgentService>()
+ private const val TAG = "BaseTrustAgentService"
+
+ fun instance(serviceClass: KClass<out BaseTrustAgentService>): BaseTrustAgentService? {
+ return instances[serviceClass]!!
+ }
+ }
+}
diff --git a/tests/TrustTests/src/android/trust/TrustTestActivity.kt b/tests/TrustTests/src/android/trust/TrustTestActivity.kt
new file mode 100644
index 000000000000..6c56feace3d7
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/TrustTestActivity.kt
@@ -0,0 +1,30 @@
+/*
+ * 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 android.trust
+
+import android.app.Activity
+import android.os.Bundle
+
+/**
+ * Activity for testing Trust.
+ */
+class TrustTestActivity : Activity() {
+
+ public override fun onCreate(icicle: Bundle?) {
+ super.onCreate(icicle)
+ setTurnScreenOn(true)
+ }
+}
diff --git a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
new file mode 100644
index 000000000000..790afd389152
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
@@ -0,0 +1,95 @@
+/*
+ * 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 android.trust.test
+
+import android.trust.BaseTrustAgentService
+import android.trust.TrustTestActivity
+import android.trust.test.lib.LockStateTrackingRule
+import android.trust.test.lib.ScreenLockRule
+import android.trust.test.lib.TrustAgentRule
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import androidx.test.uiautomator.UiDevice
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.runner.RunWith
+
+/**
+ * Test for testing revokeTrust & grantTrust for non-renewable trust.
+ *
+ * atest TrustTests:GrantAndRevokeTrustTest
+ */
+@RunWith(AndroidJUnit4::class)
+class GrantAndRevokeTrustTest {
+ private val uiDevice = UiDevice.getInstance(getInstrumentation())
+ private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java)
+ private val lockStateTrackingRule = LockStateTrackingRule()
+ private val trustAgentRule = TrustAgentRule<GrantAndRevokeTrustAgent>()
+
+ @get:Rule
+ val rule: RuleChain = RuleChain
+ .outerRule(activityScenarioRule)
+ .around(ScreenLockRule())
+ .around(lockStateTrackingRule)
+ .around(trustAgentRule)
+
+ @Before
+ fun manageTrust() {
+ trustAgentRule.agent.setManagingTrust(true)
+ }
+
+ // This test serves a baseline for Grant tests, verifying that the default behavior of the
+ // device is to lock when put to sleep
+ @Test
+ fun sleepingDeviceWithoutGrantLocksDevice() {
+ uiDevice.sleep()
+ await()
+
+ lockStateTrackingRule.assertLocked()
+ }
+
+ @Test
+ fun grantKeepsDeviceUnlocked() {
+ trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 10000, 0)
+ uiDevice.sleep()
+ await()
+
+ lockStateTrackingRule.assertUnlocked()
+ }
+
+ @Test
+ fun grantKeepsDeviceUnlocked_untilRevoked() {
+ trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, 0)
+ await()
+ uiDevice.sleep()
+ trustAgentRule.agent.revokeTrust()
+ await()
+
+ lockStateTrackingRule.assertLocked()
+ }
+
+ companion object {
+ private const val TAG = "GrantAndRevokeTrustTest"
+ private const val GRANT_MESSAGE = "granted by test"
+ private fun await() = Thread.sleep(250)
+ }
+}
+
+class GrantAndRevokeTrustAgent : BaseTrustAgentService()
diff --git a/tests/TrustTests/src/android/trust/test/LockUserTest.kt b/tests/TrustTests/src/android/trust/test/LockUserTest.kt
new file mode 100644
index 000000000000..83fc28fee818
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/LockUserTest.kt
@@ -0,0 +1,68 @@
+/*
+ * 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 android.trust.test
+
+import android.trust.BaseTrustAgentService
+import android.trust.TrustTestActivity
+import android.trust.test.lib.LockStateTrackingRule
+import android.trust.test.lib.ScreenLockRule
+import android.trust.test.lib.TrustAgentRule
+import android.util.Log
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.runner.RunWith
+
+/**
+ * Test for testing lockUser.
+ *
+ * atest TrustTests:LockUserTest
+ */
+@RunWith(AndroidJUnit4::class)
+class LockUserTest {
+ private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java)
+ private val lockStateTrackingRule = LockStateTrackingRule()
+ private val trustAgentRule = TrustAgentRule<LockUserTrustAgent>()
+
+ @get:Rule
+ val rule: RuleChain = RuleChain
+ .outerRule(activityScenarioRule)
+ .around(ScreenLockRule())
+ .around(lockStateTrackingRule)
+ .around(trustAgentRule)
+
+ @Ignore("Causes issues with subsequent tests") // TODO: Enable test
+ @Test
+ fun lockUser_locksTheDevice() {
+ Log.i(TAG, "Locking user")
+ trustAgentRule.agent.lockUser()
+ await()
+
+ assertThat(lockStateTrackingRule.lockState.locked).isTrue()
+ }
+
+ companion object {
+ private const val TAG = "LockUserTest"
+ private fun await() = Thread.sleep(250)
+ }
+}
+
+class LockUserTrustAgent : BaseTrustAgentService()
diff --git a/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt b/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt
new file mode 100644
index 000000000000..f8783fbaf121
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt
@@ -0,0 +1,81 @@
+/*
+ * 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 android.trust.test
+
+import android.app.trust.TrustManager
+import android.content.Context
+import android.trust.BaseTrustAgentService
+import android.trust.TrustTestActivity
+import android.trust.test.lib.ScreenLockRule
+import android.trust.test.lib.TrustAgentRule
+import android.util.Log
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.runner.RunWith
+
+/**
+ * Test for testing the user unlock trigger.
+ *
+ * atest TrustTests:UserUnlockRequestTest
+ */
+@RunWith(AndroidJUnit4::class)
+class UserUnlockRequestTest {
+ private val context: Context = getApplicationContext()
+ private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager
+ private val userId = context.userId
+ private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java)
+ private val trustAgentRule = TrustAgentRule<UserUnlockRequestTrustAgent>()
+
+ @get:Rule
+ val rule: RuleChain = RuleChain
+ .outerRule(activityScenarioRule)
+ .around(ScreenLockRule())
+ .around(trustAgentRule)
+
+ @Test
+ fun reportUserRequestedUnlock_propagatesToAgent() {
+ val oldCount = trustAgentRule.agent.onUserRequestedUnlockCallCount
+ trustManager.reportUserRequestedUnlock(userId)
+ await()
+
+ assertThat(trustAgentRule.agent.onUserRequestedUnlockCallCount)
+ .isEqualTo(oldCount + 1)
+ }
+
+ companion object {
+ private const val TAG = "UserUnlockRequestTest"
+ private fun await() = Thread.sleep(250)
+ }
+}
+
+class UserUnlockRequestTrustAgent : BaseTrustAgentService() {
+ var onUserRequestedUnlockCallCount: Long = 0
+ private set
+
+ override fun onUserRequestedUnlock() {
+ Log.i(TAG, "onUserRequestedUnlock")
+ onUserRequestedUnlockCallCount++
+ }
+
+ companion object {
+ private const val TAG = "UserUnlockRequestTrustAgent"
+ }
+}
diff --git a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
new file mode 100644
index 000000000000..0023af8893e2
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
@@ -0,0 +1,83 @@
+/*
+ * 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 android.trust.test.lib
+
+import android.app.trust.TrustManager
+import android.app.trust.TrustManager.TrustListener
+import android.content.Context
+import android.util.Log
+import android.view.WindowManagerGlobal
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import com.google.common.truth.Truth.assertThat
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Rule for tracking the lock state of the device based on events emitted to [TrustListener].
+ */
+class LockStateTrackingRule : TestRule {
+ private val context: Context = getApplicationContext()
+ private val windowManager = WindowManagerGlobal.getWindowManagerService()
+
+ @Volatile lateinit var lockState: LockState
+ private set
+
+ override fun apply(base: Statement, description: Description) = object : Statement() {
+ override fun evaluate() {
+ lockState = LockState(locked = windowManager.isKeyguardLocked)
+ val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager
+ val listener = Listener()
+
+ trustManager.registerTrustListener(listener)
+ try {
+ base.evaluate()
+ } finally {
+ trustManager.unregisterTrustListener(listener)
+ }
+ }
+ }
+
+ fun assertLocked() = assertThat(lockState.locked).isTrue()
+ fun assertUnlocked() = assertThat(lockState.locked).isFalse()
+
+ inner class Listener : TrustListener {
+ override fun onTrustChanged(
+ enabled: Boolean,
+ userId: Int,
+ flags: Int,
+ trustGrantedMessages: MutableList<String>
+ ) {
+ Log.d(TAG, "Device became trusted=$enabled")
+ lockState = lockState.copy(locked = !enabled)
+ }
+
+ override fun onTrustManagedChanged(enabled: Boolean, userId: Int) {
+ }
+
+ override fun onTrustError(message: CharSequence) {
+ }
+ }
+
+ data class LockState(
+ val locked: Boolean? = null
+ )
+
+ companion object {
+ private const val TAG = "LockStateTrackingRule"
+ }
+}
diff --git a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
new file mode 100644
index 000000000000..c682a00eb8b9
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
@@ -0,0 +1,105 @@
+/*
+ * 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 android.trust.test.lib
+
+import android.content.Context
+import android.util.Log
+import android.view.WindowManagerGlobal
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockscreenCredential
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Sets a screen lock on the device for the duration of the test.
+ */
+class ScreenLockRule : TestRule {
+ private val context: Context = getApplicationContext()
+ private val windowManager = WindowManagerGlobal.getWindowManagerService()
+ private val lockPatternUtils = LockPatternUtils(context)
+ private var instantLockSavedValue = false
+
+ override fun apply(base: Statement, description: Description) = object : Statement() {
+ override fun evaluate() {
+ verifyNoScreenLockAlreadySet()
+ verifyKeyguardDismissed()
+ setScreenLock()
+ setLockOnPowerButton()
+
+ try {
+ base.evaluate()
+ } finally {
+ removeScreenLock()
+ revertLockOnPowerButton()
+ }
+ }
+ }
+
+ private fun verifyNoScreenLockAlreadySet() {
+ assertWithMessage("Screen Lock must not already be set on device")
+ .that(lockPatternUtils.isSecure(context.userId))
+ .isFalse()
+ }
+
+ private fun verifyKeyguardDismissed() {
+ windowManager.dismissKeyguard(null, null)
+ Thread.sleep(250)
+ assertWithMessage("Keyguard should be unlocked")
+ .that(windowManager.isKeyguardLocked)
+ .isFalse()
+ }
+
+ private fun setScreenLock() {
+ lockPatternUtils.setLockCredential(
+ LockscreenCredential.createPin(PIN),
+ LockscreenCredential.createNone(),
+ context.userId
+ )
+ assertWithMessage("Screen Lock should now be set")
+ .that(lockPatternUtils.isSecure(context.userId))
+ .isTrue()
+ Log.i(TAG, "Device PIN set to $PIN")
+ }
+
+ private fun setLockOnPowerButton() {
+ instantLockSavedValue = lockPatternUtils.getPowerButtonInstantlyLocks(context.userId)
+ lockPatternUtils.setPowerButtonInstantlyLocks(true, context.userId)
+ }
+
+ private fun removeScreenLock() {
+ lockPatternUtils.setLockCredential(
+ LockscreenCredential.createNone(),
+ LockscreenCredential.createPin(PIN),
+ context.userId
+ )
+ Log.i(TAG, "Device PIN cleared; waiting 50 ms then dismissing Keyguard")
+ Thread.sleep(50)
+ windowManager.dismissKeyguard(null, null)
+ }
+
+ private fun revertLockOnPowerButton() {
+ lockPatternUtils.setPowerButtonInstantlyLocks(instantLockSavedValue, context.userId)
+ }
+
+ companion object {
+ private const val TAG = "ScreenLockRule"
+ private const val PIN = "0000"
+ }
+}
diff --git a/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt
new file mode 100644
index 000000000000..2a9e00276475
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt
@@ -0,0 +1,117 @@
+/*
+ * 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 android.trust.test.lib
+
+import android.app.trust.TrustManager
+import android.content.ComponentName
+import android.content.Context
+import android.trust.BaseTrustAgentService
+import android.util.Log
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import com.android.internal.widget.LockPatternUtils
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import kotlin.reflect.KClass
+
+/**
+ * Enables a trust agent and causes the system service to bind to it.
+ *
+ * The enabled agent can be accessed during the test via the [agent] property.
+ *
+ * @constructor Creates the rule. Do not use; instead, use [invoke].
+ */
+class TrustAgentRule<T : BaseTrustAgentService>(
+ private val serviceClass: KClass<T>
+) : TestRule {
+ private val context: Context = getApplicationContext()
+ private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager
+ private val lockPatternUtils = LockPatternUtils(context)
+
+ val agent get() = BaseTrustAgentService.instance(serviceClass) as T
+
+ override fun apply(base: Statement, description: Description) = object : Statement() {
+ override fun evaluate() {
+ verifyTrustServiceRunning()
+ unlockDeviceWithCredential()
+ enableTrustAgent()
+ waitForEnablement()
+
+ try {
+ verifyAgentIsRunning()
+ base.evaluate()
+ } finally {
+ disableTrustAgent()
+ }
+ }
+ }
+
+ private fun verifyTrustServiceRunning() {
+ assertWithMessage("Trust service is not running").that(trustManager).isNotNull()
+ }
+
+ private fun unlockDeviceWithCredential() {
+ Log.d(TAG, "Unlocking device with credential")
+ trustManager.reportUnlockAttempt(true, context.userId)
+ }
+
+ private fun enableTrustAgent() {
+ val componentName = ComponentName(context, serviceClass.java)
+ val userId = context.userId
+ Log.i(TAG, "Enabling trust agent ${componentName.flattenToString()} for user $userId")
+ val agents = mutableListOf(componentName)
+ .plus(lockPatternUtils.getEnabledTrustAgents(userId))
+ .distinct()
+ lockPatternUtils.setEnabledTrustAgents(agents, userId)
+ }
+
+ private fun waitForEnablement() {
+ Log.d(TAG, "Waiting for $WAIT_TIME ms")
+ Thread.sleep(WAIT_TIME)
+ Log.d(TAG, "Done waiting")
+ }
+
+ private fun verifyAgentIsRunning() {
+ assertWithMessage("${serviceClass.simpleName} should be running")
+ .that(BaseTrustAgentService.instance(serviceClass)).isNotNull()
+ }
+
+ private fun disableTrustAgent() {
+ val componentName = ComponentName(context, serviceClass.java)
+ val userId = context.userId
+ Log.i(TAG, "Disabling trust agent ${componentName.flattenToString()} for user $userId")
+ val agents = lockPatternUtils.getEnabledTrustAgents(userId).toMutableList()
+ .distinct()
+ .minus(componentName)
+ lockPatternUtils.setEnabledTrustAgents(agents, userId)
+ }
+
+ companion object {
+ /**
+ * Creates a new rule for the specified agent class. Example usage:
+ * ```
+ * @get:Rule val rule = TrustAgentRule<MyTestAgent>()
+ * ```
+ */
+ inline operator fun <reified T : BaseTrustAgentService> invoke() =
+ TrustAgentRule(T::class)
+
+ private const val TAG = "TrustAgentRule"
+ private val WAIT_TIME = 1000L
+ }
+}