diff options
69 files changed, 2196 insertions, 792 deletions
diff --git a/cmds/app_process/Android.bp b/cmds/app_process/Android.bp index a1575173ded6..6a685a79cc33 100644 --- a/cmds/app_process/Android.bp +++ b/cmds/app_process/Android.bp @@ -64,6 +64,8 @@ cc_binary { "libwilhelm", ], + header_libs: ["bionic_libc_platform_headers"], + compile_multilib: "both", cflags: [ diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp index 12083b6fe20b..815f9455471c 100644 --- a/cmds/app_process/app_main.cpp +++ b/cmds/app_process/app_main.cpp @@ -15,6 +15,7 @@ #include <android-base/macros.h> #include <binder/IPCThreadState.h> +#include <bionic/pac.h> #include <hwbinder/IPCThreadState.h> #include <utils/Log.h> #include <cutils/memory.h> @@ -182,6 +183,10 @@ int main(int argc, char* const argv[]) ALOGV("app_process main with argv: %s", argv_String.string()); } + // Because of applications that are using PAC instructions incorrectly, PAC + // is disabled in application processes for now. + ScopedDisablePAC x; + AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv)); // Process command line arguments // ignore argv[0] diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING index 23fc6f3bc72a..31c81bef7357 100644 --- a/core/java/android/app/TEST_MAPPING +++ b/core/java/android/app/TEST_MAPPING @@ -115,40 +115,42 @@ "file_patterns": ["(/|^)VoiceInteract[^/]*"] }, { - "name": "CtsContentTestCases", + "name": "CtsOsTestCases", "options": [ { + "include-annotation": "android.platform.test.annotations.Presubmit" + }, + { "exclude-annotation": "androidx.test.filters.FlakyTest" }, { "exclude-annotation": "org.junit.Ignore" }, { - "include-filter": "android.content.wm.cts" + "include-filter": "android.os.cts.StrictModeTest" } ], "file_patterns": ["(/|^)ContextImpl.java"] }, { - "name": "CtsOsTestCases", + "name": "FrameworksCoreTests", "options": [ { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { "exclude-annotation": "androidx.test.filters.FlakyTest" }, { "exclude-annotation": "org.junit.Ignore" }, { - "include-filter": "android.os.cts.StrictModeTest" + "include-filter": "android.content.ContextTest" } ], "file_patterns": ["(/|^)ContextImpl.java"] - }, + } + ], + "presubmit-large": [ { - "name": "FrameworksCoreTests", + "name": "CtsContentTestCases", "options": [ { "exclude-annotation": "androidx.test.filters.FlakyTest" @@ -157,7 +159,7 @@ "exclude-annotation": "org.junit.Ignore" }, { - "include-filter": "android.content.ContextTest" + "include-filter": "android.content.wm.cts" } ], "file_patterns": ["(/|^)ContextImpl.java"] diff --git a/core/java/android/content/TEST_MAPPING b/core/java/android/content/TEST_MAPPING index 614143e7c04d..7f1d0d1d05cf 100644 --- a/core/java/android/content/TEST_MAPPING +++ b/core/java/android/content/TEST_MAPPING @@ -1,21 +1,6 @@ { "presubmit": [ { - "name": "CtsContentTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.content.wm.cts" - } - ], - "file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java"] - }, - { "name": "CtsOsTestCases", "options": [ { @@ -51,5 +36,22 @@ ], "file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java", "(/|^)ComponentCallbacksController.java"] } + ], + "presubmit-large": [ + { + "name": "CtsContentTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "include-filter": "android.content.wm.cts" + } + ], + "file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java"] + } ] }
\ No newline at end of file diff --git a/core/java/android/content/om/TEST_MAPPING b/core/java/android/content/om/TEST_MAPPING index d8f885439b34..6185cf64ad12 100644 --- a/core/java/android/content/om/TEST_MAPPING +++ b/core/java/android/content/om/TEST_MAPPING @@ -21,7 +21,9 @@ "include-filter": "android.appsecurity.cts.OverlayHostTest" } ] - }, + } + ], + "presubmit-large": [ { "name": "CtsContentTestCases", "options": [ diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING index 8bc3734e060d..0a69413b36ea 100644 --- a/core/java/android/content/pm/TEST_MAPPING +++ b/core/java/android/content/pm/TEST_MAPPING @@ -19,6 +19,16 @@ "name": "CarrierAppIntegrationTestCases" }, { + "name": "CtsIncrementalInstallHostTestCases", + "options": [ + { + "include-filter": "android.incrementalinstall.cts.IncrementalFeatureTest" + } + ] + } + ], + "presubmit-large": [ + { "name": "CtsContentTestCases", "options": [ { @@ -31,14 +41,6 @@ "include-filter": "android.content.pm.cts" } ] - }, - { - "name": "CtsIncrementalInstallHostTestCases", - "options": [ - { - "include-filter": "android.incrementalinstall.cts.IncrementalFeatureTest" - } - ] } ], "postsubmit": [ diff --git a/core/java/android/content/res/TEST_MAPPING b/core/java/android/content/res/TEST_MAPPING index c02af59ab72e..535afd361f01 100644 --- a/core/java/android/content/res/TEST_MAPPING +++ b/core/java/android/content/res/TEST_MAPPING @@ -2,7 +2,9 @@ "presubmit": [ { "name": "CtsResourcesLoaderTests" - }, + } + ], + "presubmit-large": [ { "name": "CtsContentTestCases", "options": [ diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 56f81423db4e..b97055976e3e 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -306,22 +306,21 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan throw new IllegalArgumentException("Must supply an enrollment callback"); } - if (cancel != null) { - if (cancel.isCanceled()) { - Slog.w(TAG, "enrollment already canceled"); - return; - } else { - cancel.setOnCancelListener(new OnEnrollCancelListener()); - } + if (cancel != null && cancel.isCanceled()) { + Slog.w(TAG, "enrollment already canceled"); + return; } if (mService != null) { try { mEnrollmentCallback = callback; Trace.beginSection("FaceManager#enroll"); - mService.enroll(userId, mToken, hardwareAuthToken, mServiceReceiver, - mContext.getOpPackageName(), disabledFeatures, previewSurface, - debugConsent); + final long enrollId = mService.enroll(userId, mToken, hardwareAuthToken, + mServiceReceiver, mContext.getOpPackageName(), disabledFeatures, + previewSurface, debugConsent); + if (cancel != null) { + cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId)); + } } catch (RemoteException e) { Slog.w(TAG, "Remote exception in enroll: ", e); // Though this may not be a hardware issue, it will cause apps to give up or @@ -359,21 +358,20 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan throw new IllegalArgumentException("Must supply an enrollment callback"); } - if (cancel != null) { - if (cancel.isCanceled()) { - Slog.w(TAG, "enrollRemotely is already canceled."); - return; - } else { - cancel.setOnCancelListener(new OnEnrollCancelListener()); - } + if (cancel != null && cancel.isCanceled()) { + Slog.w(TAG, "enrollRemotely is already canceled."); + return; } if (mService != null) { try { mEnrollmentCallback = callback; Trace.beginSection("FaceManager#enrollRemotely"); - mService.enrollRemotely(userId, mToken, hardwareAuthToken, mServiceReceiver, - mContext.getOpPackageName(), disabledFeatures); + final long enrolId = mService.enrollRemotely(userId, mToken, hardwareAuthToken, + mServiceReceiver, mContext.getOpPackageName(), disabledFeatures); + if (cancel != null) { + cancel.setOnCancelListener(new OnEnrollCancelListener(enrolId)); + } } catch (RemoteException e) { Slog.w(TAG, "Remote exception in enrollRemotely: ", e); // Though this may not be a hardware issue, it will cause apps to give up or @@ -713,10 +711,10 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } } - private void cancelEnrollment() { + private void cancelEnrollment(long requestId) { if (mService != null) { try { - mService.cancelEnrollment(mToken); + mService.cancelEnrollment(mToken, requestId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1100,9 +1098,16 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } private class OnEnrollCancelListener implements OnCancelListener { + private final long mAuthRequestId; + + private OnEnrollCancelListener(long id) { + mAuthRequestId = id; + } + @Override public void onCancel() { - cancelEnrollment(); + Slog.d(TAG, "Cancel face enrollment requested for: " + mAuthRequestId); + cancelEnrollment(mAuthRequestId); } } diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index e9198246dee3..989b001ca8bf 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -76,15 +76,16 @@ interface IFaceService { void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId); // Start face enrollment - void enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver, - String opPackageName, in int [] disabledFeatures, in Surface previewSurface, boolean debugConsent); + long enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver, + String opPackageName, in int [] disabledFeatures, + in Surface previewSurface, boolean debugConsent); // Start remote face enrollment - void enrollRemotely(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver, + long enrollRemotely(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver, String opPackageName, in int [] disabledFeatures); // Cancel enrollment in progress - void cancelEnrollment(IBinder token); + void cancelEnrollment(IBinder token, long requestId); // Removes the specified face enrollment for the specified userId. void remove(IBinder token, int faceId, int userId, IFaceServiceReceiver receiver, diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index fe04e5d35784..acf9427b1241 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -183,9 +183,16 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } private class OnEnrollCancelListener implements OnCancelListener { + private final long mAuthRequestId; + + private OnEnrollCancelListener(long id) { + mAuthRequestId = id; + } + @Override public void onCancel() { - cancelEnrollment(); + Slog.d(TAG, "Cancel fingerprint enrollment requested for: " + mAuthRequestId); + cancelEnrollment(mAuthRequestId); } } @@ -646,20 +653,19 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing throw new IllegalArgumentException("Must supply an enrollment callback"); } - if (cancel != null) { - if (cancel.isCanceled()) { - Slog.w(TAG, "enrollment already canceled"); - return; - } else { - cancel.setOnCancelListener(new OnEnrollCancelListener()); - } + if (cancel != null && cancel.isCanceled()) { + Slog.w(TAG, "enrollment already canceled"); + return; } if (mService != null) { try { mEnrollmentCallback = callback; - mService.enroll(mToken, hardwareAuthToken, userId, mServiceReceiver, - mContext.getOpPackageName(), enrollReason); + final long enrollId = mService.enroll(mToken, hardwareAuthToken, userId, + mServiceReceiver, mContext.getOpPackageName(), enrollReason); + if (cancel != null) { + cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId)); + } } catch (RemoteException e) { Slog.w(TAG, "Remote exception in enroll: ", e); // Though this may not be a hardware issue, it will cause apps to give up or try @@ -1302,9 +1308,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing return allSensors.isEmpty() ? null : allSensors.get(0); } - private void cancelEnrollment() { + private void cancelEnrollment(long requestId) { if (mService != null) try { - mService.cancelEnrollment(mToken); + mService.cancelEnrollment(mToken, requestId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index ba1dc6da62a6..cbff8b11a72a 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -84,11 +84,11 @@ interface IFingerprintService { void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId); // Start fingerprint enrollment - void enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver, + long enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver, String opPackageName, int enrollReason); // Cancel enrollment in progress - void cancelEnrollment(IBinder token); + void cancelEnrollment(IBinder token, long requestId); // Any errors resulting from this call will be returned to the listener void remove(IBinder token, int fingerId, int userId, IFingerprintServiceReceiver receiver, diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 54952fc2dc39..0ac9839f9014 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9195,6 +9195,16 @@ public final class Settings { "emergency_gesture_sound_enabled"; /** + * The power button "cooldown" period in milliseconds after the Emergency gesture is + * triggered, during which single-key actions on the power button are suppressed. Cooldown + * period is disabled if set to zero. + * + * @hide + */ + public static final String EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS = + "emergency_gesture_power_button_cooldown_period_ms"; + + /** * Whether the camera launch gesture to double tap the power button when the screen is off * should be disabled. * diff --git a/core/java/android/util/apk/TEST_MAPPING b/core/java/android/util/apk/TEST_MAPPING index 4598b4ffe4f6..e1825210bfd9 100644 --- a/core/java/android/util/apk/TEST_MAPPING +++ b/core/java/android/util/apk/TEST_MAPPING @@ -1,21 +1,23 @@ { "presubmit": [ { - "name": "CtsContentTestCases", + "name": "FrameworksCoreTests", "options": [ { - "include-filter": "android.content.pm.cts.PackageManagerShellCommandIncrementalTest" - }, - { - "include-filter": "android.content.pm.cts.PackageManagerShellCommandTest" + "include-filter": "android.util.apk.SourceStampVerifierTest" } ] - }, + } + ], + "presubmit-large": [ { - "name": "FrameworksCoreTests", + "name": "CtsContentTestCases", "options": [ { - "include-filter": "android.util.apk.SourceStampVerifierTest" + "include-filter": "android.content.pm.cts.PackageManagerShellCommandIncrementalTest" + }, + { + "include-filter": "android.content.pm.cts.PackageManagerShellCommandTest" } ] } diff --git a/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java b/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java index 328429c6f96e..e690da2d0377 100644 --- a/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java +++ b/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java @@ -17,6 +17,7 @@ package android.content; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; import android.app.ActivityManager; import android.app.activity.LocalProvider; @@ -58,10 +59,12 @@ abstract class AbstractCrossUserContentResolverTest { @Before public void setUp() throws Exception { mContext = InstrumentationRegistry.getContext(); + final PackageManager pm = mContext.getPackageManager(); + assumeTrue("device doesn't have the " + PackageManager.FEATURE_MANAGED_USERS + " feature", + pm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)); mUm = UserManager.get(mContext); final UserInfo userInfo = createUser(); mCrossUserId = userInfo.id; - final PackageManager pm = mContext.getPackageManager(); pm.installExistingPackageAsUser(mContext.getPackageName(), mCrossUserId); unlockUser(); diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml index 8fef67b69747..c3cd04655178 100644 --- a/libs/WindowManager/Shell/res/values-te/strings.xml +++ b/libs/WindowManager/Shell/res/values-te/strings.xml @@ -36,16 +36,16 @@ <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ప్రత్యామ్నాయ డిస్ప్లేలో యాప్ పని చేయకపోవచ్చు."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ప్రత్యామ్నాయ డిస్ప్లేల్లో ప్రారంభానికి యాప్ మద్దతు లేదు."</string> <string name="accessibility_divider" msgid="703810061635792791">"విభజన స్క్రీన్ విభాగిని"</string> - <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ఎడమవైపు పూర్తి స్క్రీన్"</string> + <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ఎడమవైపు ఫుల్-స్క్రీన్"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ఎడమవైపు 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ఎడమవైపు 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ఎడమవైపు 30%"</string> - <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"కుడివైపు పూర్తి స్క్రీన్"</string> - <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ఎగువ పూర్తి స్క్రీన్"</string> + <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"కుడివైపు ఫుల్-స్క్రీన్"</string> + <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ఎగువ ఫుల్-స్క్రీన్"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ఎగువ 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ఎగువ 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ఎగువ 30%"</string> - <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"దిగువ పూర్తి స్క్రీన్"</string> + <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"దిగువ ఫుల్-స్క్రీన్"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"వన్-హ్యాండెడ్ మోడ్ను ఉపయోగించడం"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"నిష్క్రమించడానికి, స్క్రీన్ కింది భాగం నుండి పైకి స్వైప్ చేయండి లేదా యాప్ పైన ఎక్కడైనా ట్యాప్ చేయండి"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"వన్-హ్యాండెడ్ మోడ్ను ప్రారంభిస్తుంది"</string> diff --git a/libs/WindowManager/Shell/res/values-te/strings_tv.xml b/libs/WindowManager/Shell/res/values-te/strings_tv.xml index 47489efbc4c2..b9e8d762eda9 100644 --- a/libs/WindowManager/Shell/res/values-te/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-te/strings_tv.xml @@ -20,5 +20,5 @@ <string name="notification_channel_tv_pip" msgid="2576686079160402435">"పిక్చర్-ఇన్-పిక్చర్"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(శీర్షిక లేని ప్రోగ్రామ్)"</string> <string name="pip_close" msgid="9135220303720555525">"PIPని మూసివేయి"</string> - <string name="pip_fullscreen" msgid="7278047353591302554">"పూర్తి స్క్రీన్"</string> + <string name="pip_fullscreen" msgid="7278047353591302554">"ఫుల్-స్క్రీన్"</string> </resources> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 8b87df44c52c..4c77f6a7e00d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -134,6 +134,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider; private StageCoordinator mStageCoordinator; + // Only used for the legacy recents animation from splitscreen to allow the tasks to be animated + // outside the bounds of the roots by being reparented into a higher level fullscreen container + private SurfaceControl mSplitTasksContainerLayer; public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, Context context, @@ -364,20 +367,24 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, RemoteAnimationTarget[] apps) { if (apps.length < 2) return null; + SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); + if (mSplitTasksContainerLayer != null) { + // Remove the previous layer before recreating + transaction.remove(mSplitTasksContainerLayer); + } final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession()) .setContainerLayer() .setName("RecentsAnimationSplitTasks") .setHidden(false) .setCallsite("SplitScreenController#onGoingtoRecentsLegacy"); mRootTDAOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, builder); - SurfaceControl sc = builder.build(); - SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); + mSplitTasksContainerLayer = builder.build(); // Ensure that we order these in the parent in the right z-order as their previous order Arrays.sort(apps, (a1, a2) -> a1.prefixOrderIndex - a2.prefixOrderIndex); int layer = 1; for (RemoteAnimationTarget appTarget : apps) { - transaction.reparent(appTarget.leash, sc); + transaction.reparent(appTarget.leash, mSplitTasksContainerLayer); transaction.setPosition(appTarget.leash, appTarget.screenSpaceBounds.left, appTarget.screenSpaceBounds.top); transaction.setLayer(appTarget.leash, layer++); 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 5d1d159e63e6..38c1aff0a62c 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 @@ -365,8 +365,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) { + // Ensure divider is invisible before transition. + setDividerVisibility(false /* visible */); // Init divider first to make divider leash for remote animation target. - setDividerVisibility(true /* visible */); + mSplitLayout.init(); // Set false to avoid record new bounds with old task still on top; mShouldUpdateRecents = false; final WindowContainerTransaction wct = new WindowContainerTransaction(); @@ -396,6 +398,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, public void onAnimationFinished() throws RemoteException { mIsDividerRemoteAnimating = false; mShouldUpdateRecents = true; + setDividerVisibility(true /* visible */); mSyncQueue.queue(evictWct); mSyncQueue.runInSync(t -> applyDividerVisibility(t)); finishedCallback.onAnimationFinished(); @@ -420,6 +423,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, public void onAnimationCancelled() { mIsDividerRemoteAnimating = false; mShouldUpdateRecents = true; + setDividerVisibility(true /* visible */); mSyncQueue.queue(evictWct); mSyncQueue.runInSync(t -> applyDividerVisibility(t)); try { diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp index 05278f24ebbd..4003f0b65fb5 100755 --- a/libs/hwui/jni/Bitmap.cpp +++ b/libs/hwui/jni/Bitmap.cpp @@ -686,16 +686,14 @@ static binder_status_t readBlob(AParcel* parcel, T inPlaceCallback, U ashmemCall } return data->ptr != nullptr; })); - inPlaceCallback(std::move(data.ptr), data.size); - return STATUS_OK; + return inPlaceCallback(std::move(data.ptr), data.size); } else if (type == BlobType::ASHMEM) { int rawFd = -1; int32_t size = 0; ON_ERROR_RETURN(AParcel_readInt32(parcel, &size)); ON_ERROR_RETURN(AParcel_readParcelFileDescriptor(parcel, &rawFd)); android::base::unique_fd fd(rawFd); - ashmemCallback(std::move(fd), size); - return STATUS_OK; + return ashmemCallback(std::move(fd), size); } else { // Although the above if/else was "exhaustive" guard against unknown types return STATUS_UNKNOWN_ERROR; @@ -768,7 +766,7 @@ static binder_status_t writeBlob(AParcel* parcel, const int32_t size, const void // framework, we may need to update this maximum size. static constexpr size_t kMaxColorSpaceSerializedBytes = 80; -static constexpr auto RuntimeException = "java/lang/RuntimeException"; +static constexpr auto BadParcelableException = "android/os/BadParcelableException"; static bool validateImageInfo(const SkImageInfo& info, int32_t rowBytes) { // TODO: Can we avoid making a SkBitmap for this? @@ -809,7 +807,7 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { kRGB_565_SkColorType != colorType && kARGB_4444_SkColorType != colorType && kAlpha_8_SkColorType != colorType) { - jniThrowExceptionFmt(env, RuntimeException, + jniThrowExceptionFmt(env, BadParcelableException, "Bitmap_createFromParcel unknown colortype: %d\n", colorType); return NULL; } @@ -821,7 +819,7 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { return NULL; } if (!Bitmap::computeAllocationSize(rowBytes, height, &allocationSize)) { - jniThrowExceptionFmt(env, RuntimeException, + jniThrowExceptionFmt(env, BadParcelableException, "Received bad bitmap size: width=%d, height=%d, rowBytes=%d", width, height, rowBytes); return NULL; @@ -831,13 +829,23 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { p.get(), // In place callback [&](std::unique_ptr<int8_t[]> buffer, int32_t size) { + if (allocationSize > size) { + android_errorWriteLog(0x534e4554, "213169612"); + return STATUS_BAD_VALUE; + } nativeBitmap = Bitmap::allocateHeapBitmap(allocationSize, imageInfo, rowBytes); if (nativeBitmap) { - memcpy(nativeBitmap->pixels(), buffer.get(), size); + memcpy(nativeBitmap->pixels(), buffer.get(), allocationSize); + return STATUS_OK; } + return STATUS_NO_MEMORY; }, // Ashmem callback [&](android::base::unique_fd fd, int32_t size) { + if (allocationSize > size) { + android_errorWriteLog(0x534e4554, "213169612"); + return STATUS_BAD_VALUE; + } int flags = PROT_READ; if (isMutable) { flags |= PROT_WRITE; @@ -846,18 +854,21 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { if (addr == MAP_FAILED) { const int err = errno; ALOGW("mmap failed, error %d (%s)", err, strerror(err)); - return; + return STATUS_NO_MEMORY; } nativeBitmap = Bitmap::createFrom(imageInfo, rowBytes, fd.release(), addr, size, !isMutable); + return STATUS_OK; }); - if (error != STATUS_OK) { + + if (error != STATUS_OK && error != STATUS_NO_MEMORY) { // TODO: Stringify the error, see signalExceptionForError in android_util_Binder.cpp - jniThrowExceptionFmt(env, RuntimeException, "Failed to read from Parcel, error=%d", error); + jniThrowExceptionFmt(env, BadParcelableException, "Failed to read from Parcel, error=%d", + error); return nullptr; } - if (!nativeBitmap) { - jniThrowRuntimeException(env, "Could not allocate java pixel ref."); + if (error == STATUS_NO_MEMORY || !nativeBitmap) { + jniThrowRuntimeException(env, "Could not allocate bitmap data."); return nullptr; } diff --git a/packages/DynamicSystemInstallationService/res/values-gl/strings.xml b/packages/DynamicSystemInstallationService/res/values-gl/strings.xml index 58a80a7f4999..7ead44b27c3d 100644 --- a/packages/DynamicSystemInstallationService/res/values-gl/strings.xml +++ b/packages/DynamicSystemInstallationService/res/values-gl/strings.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="keyguard_description" msgid="8582605799129954556">"Pon o teu contrasinal e vai a Dynamic System Updates"</string> + <string name="keyguard_description" msgid="8582605799129954556">"Pon o teu contrasinal e vai a Actualizacións dinámicas do sistema"</string> <string name="notification_install_completed" msgid="6252047868415172643">"O sistema dinámico está listo. Para utilizalo, reinicia o dispositivo."</string> <string name="notification_install_inprogress" msgid="7383334330065065017">"Instalación en curso"</string> <string name="notification_install_failed" msgid="4066039210317521404">"Produciuse un erro durante a instalación"</string> diff --git a/packages/PrintSpooler/res/values-te/strings.xml b/packages/PrintSpooler/res/values-te/strings.xml index a1ed2caa9999..038029d6a43f 100644 --- a/packages/PrintSpooler/res/values-te/strings.xml +++ b/packages/PrintSpooler/res/values-te/strings.xml @@ -88,7 +88,7 @@ <string name="no_connection_to_printer" msgid="2159246915977282728">"ప్రింటర్కు కనెక్షన్ లేదు"</string> <string name="reason_unknown" msgid="5507940196503246139">"తెలియదు"</string> <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g>ని ఉపయోగించాలా?"</string> - <string name="print_service_security_warning_summary" msgid="1427434625361692006">"మీ పత్రం ప్రింటర్కు వెళ్లే మార్గంలో ఒకటి లేదా అంతకంటే ఎక్కువ సర్వర్ల గుండా వెళ్లవచ్చు."</string> + <string name="print_service_security_warning_summary" msgid="1427434625361692006">"మీ డాక్యుమెంట్ ప్రింటర్కు వెళ్లే మార్గంలో ఒకటి లేదా అంతకంటే ఎక్కువ సర్వర్ల గుండా వెళ్లవచ్చు."</string> <string-array name="color_mode_labels"> <item msgid="7602948745415174937">"నలుపు & తెలుపు"</item> <item msgid="2762241247228983754">"రంగు"</item> diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml index ca299f008d98..34589ce6b375 100644 --- a/packages/SettingsLib/res/values-fi/strings.xml +++ b/packages/SettingsLib/res/values-fi/strings.xml @@ -179,7 +179,7 @@ <string name="tts_status_checking" msgid="8026559918948285013">"Tarkistetaan…"</string> <string name="tts_engine_settings_title" msgid="7849477533103566291">"Asetukset: <xliff:g id="TTS_ENGINE_NAME">%s</xliff:g>"</string> <string name="tts_engine_settings_button" msgid="477155276199968948">"Käynnistä moottorin asetukset"</string> - <string name="tts_engine_preference_section_title" msgid="3861562305498624904">"Ensisijainen kone"</string> + <string name="tts_engine_preference_section_title" msgid="3861562305498624904">"Ensisijainen moottori"</string> <string name="tts_general_section_title" msgid="8919671529502364567">"Yleiset"</string> <string name="tts_reset_speech_pitch_title" msgid="7149398585468413246">"Palauta äänenkorkeus"</string> <string name="tts_reset_speech_pitch_summary" msgid="6822904157021406449">"Palauta tekstin lukemisen oletusäänenkorkeus"</string> @@ -190,8 +190,8 @@ <item msgid="1158955023692670059">"Nopea"</item> <item msgid="5664310435707146591">"Nopeampi"</item> <item msgid="5491266922147715962">"Hyvin nopea"</item> - <item msgid="7659240015901486196">"Nopea"</item> - <item msgid="7147051179282410945">"Erittäin nopea"</item> + <item msgid="7659240015901486196">"Vauhdikas"</item> + <item msgid="7147051179282410945">"Erittäin vauhdikas"</item> <item msgid="581904787661470707">"Nopein"</item> </string-array> <string name="choose_profile" msgid="343803890897657450">"Valitse profiili"</string> diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 9f883960981b..6671073cd698 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -272,6 +272,8 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.EMERGENCY_GESTURE_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.EMERGENCY_GESTURE_SOUND_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS, + NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(Secure.ADAPTIVE_CONNECTIVITY_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put( Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS, NONE_NEGATIVE_LONG_VALIDATOR); diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml index a946318cb313..0a2d226a33eb 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml @@ -16,7 +16,6 @@ ** limitations under the License. */ --> - <com.android.keyguard.KeyguardPINView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/res-auto" @@ -186,8 +185,6 @@ </androidx.constraintlayout.widget.ConstraintLayout> - - <include layout="@layout/keyguard_eca" android:id="@+id/keyguard_selector_fade_container" android:layout_width="match_parent" diff --git a/packages/SystemUI/res-keyguard/values-land/dimens.xml b/packages/SystemUI/res-keyguard/values-land/dimens.xml index 6342b9c0c7f0..af284a87295b 100644 --- a/packages/SystemUI/res-keyguard/values-land/dimens.xml +++ b/packages/SystemUI/res-keyguard/values-land/dimens.xml @@ -19,7 +19,7 @@ --> <resources> <dimen name="num_pad_row_margin_bottom">3dp</dimen> - <dimen name="keyguard_eca_top_margin">0dp</dimen> + <dimen name="keyguard_eca_top_margin">2dp</dimen> <dimen name="keyguard_eca_bottom_margin">2dp</dimen> <dimen name="keyguard_password_height">26dp</dimen> <dimen name="num_pad_entry_row_margin_bottom">0dp</dimen> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java index 099dd5d82a10..a7840cbf2789 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java @@ -18,6 +18,7 @@ package com.android.keyguard; import android.content.Context; import android.content.res.ColorStateList; +import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.Color; import android.os.Handler; @@ -58,7 +59,8 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp private boolean mBouncerVisible; private boolean mAltBouncerShowing; private ViewGroup mContainer; - private int mTopMargin; + private int mContainerTopMargin; + private int mLastOrientation = -1; public KeyguardMessageArea(Context context, AttributeSet attrs) { super(context, attrs); @@ -74,16 +76,28 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp mContainer = getRootView().findViewById(R.id.keyguard_message_area_container); } - void onConfigChanged() { + void onConfigChanged(Configuration newConfig) { final int newTopMargin = SystemBarUtils.getStatusBarHeight(getContext()); - if (mTopMargin == newTopMargin) { - return; - } - mTopMargin = newTopMargin; - ViewGroup.MarginLayoutParams lp = + if (mContainerTopMargin != newTopMargin) { + mContainerTopMargin = newTopMargin; + ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) mContainer.getLayoutParams(); - lp.topMargin = mTopMargin; - mContainer.setLayoutParams(lp); + lp.topMargin = mContainerTopMargin; + mContainer.setLayoutParams(lp); + } + + if (mLastOrientation != newConfig.orientation) { + mLastOrientation = newConfig.orientation; + int messageAreaTopMargin = 0; + if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { + messageAreaTopMargin = mContext.getResources().getDimensionPixelSize( + R.dimen.keyguard_lock_padding); + } + + ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams(); + lp.topMargin = messageAreaTopMargin; + setLayoutParams(lp); + } } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java index 05318bb0df78..659aadd69614 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java @@ -50,7 +50,7 @@ public class KeyguardMessageAreaController extends ViewController<KeyguardMessag private ConfigurationListener mConfigurationListener = new ConfigurationListener() { @Override public void onConfigChanged(Configuration newConfig) { - mView.onConfigChanged(); + mView.onConfigChanged(newConfig); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java index 1efda7edee2f..5115aba26ee7 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java @@ -21,10 +21,13 @@ import static com.android.systemui.statusbar.policy.DevicePostureController.DEVI import android.content.Context; import android.content.res.Configuration; +import android.content.res.Resources; import android.util.AttributeSet; import android.view.View; +import android.view.ViewGroup; import android.view.animation.AnimationUtils; +import androidx.constraintlayout.helper.widget.Flow; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintSet; @@ -87,48 +90,45 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { } private void updateMargins() { + Resources res = mContext.getResources(); + // Re-apply everything to the keys... - int bottomMargin = mContext.getResources().getDimensionPixelSize( - R.dimen.num_pad_entry_row_margin_bottom); - int rightMargin = mContext.getResources().getDimensionPixelSize( - R.dimen.num_pad_key_margin_end); - String ratio = mContext.getResources().getString(R.string.num_pad_key_ratio); - - // mView contains all Views that make up the PIN pad; row0 = the entry test field, then - // rows 1-4 contain the buttons. Iterate over all views that make up the buttons in the pad, - // and re-set all the margins. - for (int row = 1; row < 5; row++) { - for (int column = 0; column < 3; column++) { - View key = mViews[row][column]; - - ConstraintLayout.LayoutParams lp = - (ConstraintLayout.LayoutParams) key.getLayoutParams(); - - lp.dimensionRatio = ratio; - - // Don't set any margins on the last row of buttons. - if (row != 4) { - lp.bottomMargin = bottomMargin; - } - - // Don't set margins on the rightmost buttons. - if (column != 2) { - lp.rightMargin = rightMargin; - } - - key.setLayoutParams(lp); - } - } + int verticalMargin = res.getDimensionPixelSize(R.dimen.num_pad_entry_row_margin_bottom); + int horizontalMargin = res.getDimensionPixelSize(R.dimen.num_pad_key_margin_end); + String ratio = res.getString(R.string.num_pad_key_ratio); + + Flow flow = (Flow) mContainer.findViewById(R.id.flow1); + flow.setHorizontalGap(horizontalMargin); + flow.setVerticalGap(verticalMargin); // Update the guideline based on the device posture... - float halfOpenPercentage = - mContext.getResources().getFloat(R.dimen.half_opened_bouncer_height_ratio); + float halfOpenPercentage = res.getFloat(R.dimen.half_opened_bouncer_height_ratio); ConstraintSet cs = new ConstraintSet(); cs.clone(mContainer); cs.setGuidelinePercent(R.id.pin_pad_top_guideline, mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED ? halfOpenPercentage : 0.0f); cs.applyTo(mContainer); + + // Password entry area + int passwordHeight = res.getDimensionPixelSize(R.dimen.keyguard_password_height); + View pinEntry = mContainer.findViewById(R.id.pinEntry); + ViewGroup.LayoutParams lp = pinEntry.getLayoutParams(); + lp.height = passwordHeight; + pinEntry.setLayoutParams(lp); + + // Below row0 + View row0 = mContainer.findViewById(R.id.row0); + row0.setPadding(0, 0, 0, verticalMargin); + + // Above the emergency contact area + int marginTop = res.getDimensionPixelSize(R.dimen.keyguard_eca_top_margin); + View eca = findViewById(R.id.keyguard_selector_fade_container); + if (eca != null) { + ViewGroup.MarginLayoutParams mLp = (ViewGroup.MarginLayoutParams) eca.getLayoutParams(); + mLp.topMargin = marginTop; + eca.setLayoutParams(mLp); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt index b7404dfeb1cc..dfbe348c6ede 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt @@ -37,7 +37,7 @@ class BiometricDisplayListener( private val onChanged: () -> Unit ) : DisplayManager.DisplayListener { - private var lastRotation = context.display?.rotation ?: Surface.ROTATION_0 + private var lastRotation = Surface.ROTATION_0 override fun onDisplayAdded(displayId: Int) {} override fun onDisplayRemoved(displayId: Int) {} @@ -63,6 +63,7 @@ class BiometricDisplayListener( /** Listen for changes. */ fun enable() { + lastRotation = context.display?.rotation ?: Surface.ROTATION_0 displayManager.registerDisplayListener(this, handler) } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 3e9d6b0fa362..250c16c81eac 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -480,8 +480,33 @@ public class UdfpsController implements DozeReceiver { final long sinceLastLog = mSystemClock.elapsedRealtime() - mTouchLogTime; if (!isIlluminationRequested && !mGoodCaptureReceived && !exceedsVelocityThreshold) { - onFingerDown((int) event.getRawX(), (int) event.getRawY(), minor, - major); + final int rawX = (int) event.getRawX(); + final int rawY = (int) event.getRawY(); + // Default coordinates assume portrait mode. + int x = rawX; + int y = rawY; + + // Gets the size based on the current rotation of the display. + Point p = new Point(); + mContext.getDisplay().getRealSize(p); + + // Transform x, y to portrait mode if the device is in landscape mode. + switch (mContext.getDisplay().getRotation()) { + case Surface.ROTATION_90: + x = p.y - rawY; + y = rawX; + break; + + case Surface.ROTATION_270: + x = rawY; + y = p.x - rawX; + break; + + default: + // Do nothing to stay in portrait mode. + } + + onFingerDown(x, y, minor, major); Log.v(TAG, "onTouch | finger down: " + touchInfo); mTouchLogTime = mSystemClock.elapsedRealtime(); mPowerManager.userActivity(mSystemClock.uptimeMillis(), diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index ca63ec269bf4..0d11070fd220 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -86,6 +86,7 @@ import androidx.constraintlayout.widget.ConstraintLayout; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; +import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.shared.system.InputMonitorCompat; import com.android.systemui.shared.system.QuickStepContract; @@ -162,6 +163,7 @@ public class ScreenshotView extends FrameLayout implements private GestureDetector mSwipeDetector; private SwipeDismissHandler mSwipeDismissHandler; private InputMonitorCompat mInputMonitor; + private InputChannelCompat.InputEventReceiver mInputEventReceiver; private boolean mShowScrollablePreview; private String mPackageName = ""; @@ -302,8 +304,8 @@ public class ScreenshotView extends FrameLayout implements private void startInputListening() { stopInputListening(); mInputMonitor = new InputMonitorCompat("Screenshot", Display.DEFAULT_DISPLAY); - mInputMonitor.getInputReceiver(Looper.getMainLooper(), Choreographer.getInstance(), - ev -> { + mInputEventReceiver = mInputMonitor.getInputReceiver( + Looper.getMainLooper(), Choreographer.getInstance(), ev -> { if (ev instanceof MotionEvent) { MotionEvent event = (MotionEvent) ev; if (event.getActionMasked() == MotionEvent.ACTION_DOWN @@ -320,6 +322,10 @@ public class ScreenshotView extends FrameLayout implements mInputMonitor.dispose(); mInputMonitor = null; } + if (mInputEventReceiver != null) { + mInputEventReceiver.dispose(); + mInputEventReceiver = null; + } } @Override // ViewGroup diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java index 5993f1dee3a7..c29905bc7008 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java @@ -384,9 +384,9 @@ public class NotificationGroupManagerLegacy implements // * Only necessary when all notifications in the group use GROUP_ALERT_SUMMARY // * Only necessary when at least one notification in the group is on a priority channel if (group.summary.getSbn().getNotification().getGroupAlertBehavior() - != Notification.GROUP_ALERT_SUMMARY) { + == Notification.GROUP_ALERT_CHILDREN) { if (SPEW) { - Log.d(TAG, "getPriorityConversationAlertOverride: summary != GROUP_ALERT_SUMMARY"); + Log.d(TAG, "getPriorityConversationAlertOverride: summary == GROUP_ALERT_CHILDREN"); } return null; } @@ -529,8 +529,10 @@ public class NotificationGroupManagerLegacy implements mIsolatedEntries.put(entry.getKey(), entry.getSbn()); if (groupKeysChanged) { updateSuppression(mGroupMap.get(oldGroupKey)); - updateSuppression(mGroupMap.get(newGroupKey)); } + // Always update the suppression of the group from which you're isolated, in case + // this entry was or now is the alertOverride for that group. + updateSuppression(mGroupMap.get(newGroupKey)); } else if (!wasGroupChild && isGroupChild) { onEntryBecomingChild(entry); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 73a48c3b5cb0..c14e2f96320c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -130,6 +130,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public static final float BACKGROUND_ALPHA_DIMMED = 0.7f; private static final String TAG = "StackScroller"; + private static final boolean SPEW = Log.isLoggable(TAG, Log.VERBOSE); // Usage: // adb shell setprop persist.debug.nssl true && adb reboot @@ -3156,6 +3157,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable AnimationEvent event = new AnimationEvent(row, type); event.headsUpFromBottom = onBottom; mAnimationEvents.add(event); + if (SPEW) { + Log.v(TAG, "Generating HUN animation event: " + + " isHeadsUp=" + isHeadsUp + + " type=" + type + + " onBottom=" + onBottom + + " row=" + row.getEntry().getKey()); + } } mHeadsUpChangeAnimations.clear(); mAddedHeadsUpChildren.clear(); @@ -4679,7 +4687,22 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) { - if (mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed)) { + final boolean add = mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed); + if (SPEW) { + Log.v(TAG, "generateHeadsUpAnimation:" + + " willAdd=" + add + + " isHeadsUp=" + isHeadsUp + + " row=" + row.getEntry().getKey()); + } + if (add) { + // If we're hiding a HUN we just started showing THIS FRAME, then remove that event, + // and do not add the disappear event either. + if (!isHeadsUp && mHeadsUpChangeAnimations.remove(new Pair<>(row, true))) { + if (SPEW) { + Log.v(TAG, "generateHeadsUpAnimation: previous hun appear animation cancelled"); + } + return; + } mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp)); mNeedsAnimation = true; if (!mIsExpanded && !mWillExpand && !isHeadsUp) { 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 83ab8cb80b29..4ba794fe8408 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -6,6 +6,7 @@ import android.animation.ValueAnimator import android.content.Context import android.database.ContentObserver import android.os.Handler +import android.os.PowerManager import android.provider.Settings import android.view.Surface import android.view.View @@ -50,9 +51,10 @@ class UnlockedScreenOffAnimationController @Inject constructor( private val keyguardViewMediatorLazy: dagger.Lazy<KeyguardViewMediator>, private val keyguardStateController: KeyguardStateController, private val dozeParameters: dagger.Lazy<DozeParameters>, - private val globalSettings: GlobalSettings + private val globalSettings: GlobalSettings, + private val powerManager: PowerManager, + private val handler: Handler = Handler() ) : WakefulnessLifecycle.Observer { - private val handler = Handler() private lateinit var statusBar: StatusBar private lateinit var lightRevealScrim: LightRevealScrim @@ -205,10 +207,18 @@ class UnlockedScreenOffAnimationController @Inject constructor( lightRevealAnimationPlaying = true lightRevealAnimator.start() handler.postDelayed({ - aodUiAnimationPlaying = true - - // Show AOD. That'll cause the KeyguardVisibilityHelper to call #animateInKeyguard. - statusBar.notificationPanelViewController.showAodUi() + // Only run this callback if the device is sleeping (not interactive). This callback + // is removed in onStartedWakingUp, but since that event is asynchronously + // dispatched, a race condition could make it possible for this callback to be run + // as the device is waking up. That results in the AOD UI being shown while we wake + // up, with unpredictable consequences. + if (!powerManager.isInteractive) { + aodUiAnimationPlaying = true + + // Show AOD. That'll cause the KeyguardVisibilityHelper to call + // #animateInKeyguard. + statusBar.notificationPanelViewController.showAodUi() + } }, (ANIMATE_IN_KEYGUARD_DELAY * animatorDurationScale).toLong()) } else { decidedToAnimateGoingToSleep = false diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java new file mode 100644 index 000000000000..40f335dfc20d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java @@ -0,0 +1,193 @@ +/* + * 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 static com.android.systemui.biometrics.BiometricDisplayListener.SensorType.SideFingerprint; +import static com.android.systemui.biometrics.BiometricDisplayListener.SensorType.UnderDisplayFingerprint; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.display.DisplayManager; +import android.os.Handler; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunWithLooper; +import android.view.Display; +import android.view.Surface; +import android.view.Surface.Rotation; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import kotlin.Unit; +import kotlin.jvm.functions.Function0; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@RunWithLooper(setAsMainLooper = true) +public class BiometricDisplayListenerTest extends SysuiTestCase { + + // Dependencies + @Mock private DisplayManager mDisplayManager; + @Mock private Display mDisplay; + @Mock private Function0<Unit> mOnChangedCallback; + @Mock private UnderDisplayFingerprint mUdfpsType; + @Mock private SideFingerprint mSidefpsType; + private Handler mHandler; + private Context mContextSpy; + + // Captors + @Captor private ArgumentCaptor<DisplayManager.DisplayListener> mDisplayListenerCaptor; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + // Set up mocks + mContextSpy = spy(mContext); + when(mContextSpy.getDisplay()).thenReturn(mDisplay); + + // Create a real handler with a TestableLooper. + TestableLooper testableLooper = TestableLooper.get(this); + mHandler = new Handler(testableLooper.getLooper()); + } + + @Test + public void registersDisplayListener_whenEnabled() { + BiometricDisplayListener listener = new BiometricDisplayListener( + mContextSpy, mDisplayManager, mHandler, mUdfpsType, mOnChangedCallback); + + listener.enable(); + verify(mDisplayManager).registerDisplayListener(any(), same(mHandler)); + } + + @Test + public void unregistersDisplayListener_whenDisabled() { + BiometricDisplayListener listener = new BiometricDisplayListener( + mContextSpy, mDisplayManager, mHandler, mUdfpsType, mOnChangedCallback); + + listener.enable(); + listener.disable(); + verify(mDisplayManager).unregisterDisplayListener(any()); + } + + @Test + public void detectsRotationChanges_forUdfps_relativeToRotationWhenEnabled() { + // Create a listener when the rotation is portrait. + when(mDisplay.getRotation()).thenReturn(Surface.ROTATION_0); + BiometricDisplayListener listener = new BiometricDisplayListener( + mContextSpy, mDisplayManager, mHandler, mUdfpsType, mOnChangedCallback); + + // Rotate the device to landscape and then enable the listener. + when(mDisplay.getRotation()).thenReturn(Surface.ROTATION_90); + listener.enable(); + verify(mDisplayManager).registerDisplayListener(mDisplayListenerCaptor.capture(), + same(mHandler)); + + // Rotate the device back to portrait and ensure the rotation is detected. + when(mDisplay.getRotation()).thenReturn(Surface.ROTATION_0); + mDisplayListenerCaptor.getValue().onDisplayChanged(999); + verify(mOnChangedCallback).invoke(); + } + + @Test + public void callsOnChanged_forUdfps_onlyWhenRotationChanges() { + final @Rotation int[] rotations = + new int[]{ + Surface.ROTATION_0, + Surface.ROTATION_90, + Surface.ROTATION_180, + Surface.ROTATION_270 + }; + + for (@Rotation int rot1 : rotations) { + for (@Rotation int rot2 : rotations) { + // Make the third rotation the same as the first one to simplify this test. + @Rotation int rot3 = rot1; + + // Clean up prior interactions. + reset(mDisplayManager); + reset(mDisplay); + reset(mOnChangedCallback); + + // Set up the mock for 3 invocations. + when(mDisplay.getRotation()).thenReturn(rot1, rot2, rot3); + + BiometricDisplayListener listener = new BiometricDisplayListener( + mContextSpy, mDisplayManager, mHandler, mUdfpsType, mOnChangedCallback); + listener.enable(); + + // The listener should record the current rotation and register a display listener. + verify(mDisplay).getRotation(); + verify(mDisplayManager) + .registerDisplayListener(mDisplayListenerCaptor.capture(), same(mHandler)); + + // Test the first rotation since the listener was enabled. + mDisplayListenerCaptor.getValue().onDisplayChanged(123); + if (rot2 != rot1) { + verify(mOnChangedCallback).invoke(); + } else { + verify(mOnChangedCallback, never()).invoke(); + } + + // Test continued rotations. + mDisplayListenerCaptor.getValue().onDisplayChanged(123); + if (rot3 != rot2) { + verify(mOnChangedCallback, times(2)).invoke(); + } else { + verify(mOnChangedCallback, never()).invoke(); + } + } + } + } + + @Test + public void callsOnChanged_forSideFingerprint_whenAnythingDisplayChanges() { + // Any rotation will do for this test, we just need to return something. + when(mDisplay.getRotation()).thenReturn(Surface.ROTATION_0); + + BiometricDisplayListener listener = new BiometricDisplayListener( + mContextSpy, mDisplayManager, mHandler, mSidefpsType, mOnChangedCallback); + listener.enable(); + + // The listener should register a display listener. + verify(mDisplayManager) + .registerDisplayListener(mDisplayListenerCaptor.capture(), same(mHandler)); + + // mOnChangedCallback should be invoked for all calls to onDisplayChanged. + mDisplayListenerCaptor.getValue().onDisplayChanged(123); + mDisplayListenerCaptor.getValue().onDisplayChanged(123); + verify(mOnChangedCallback, times(2)).invoke(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java index 1be27da27d25..6d170b673cc3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java @@ -178,20 +178,68 @@ public class NotificationGroupManagerLegacyTest extends SysuiTestCase { } /** + * Helper for testing various sibling counts + */ + private void helpTestAlertOverrideWithSiblings(int numSiblings) { + helpTestAlertOverride( + /* numSiblings */ numSiblings, + /* summaryAlert */ Notification.GROUP_ALERT_SUMMARY, + /* childAlert */ Notification.GROUP_ALERT_SUMMARY, + /* siblingAlert */ Notification.GROUP_ALERT_SUMMARY, + /* expectAlertOverride */ true); + } + + @Test + public void testAlertOverrideWithParentAlertAll() { + // tests that summary can have GROUP_ALERT_ALL and this still works + helpTestAlertOverride( + /* numSiblings */ 1, + /* summaryAlert */ Notification.GROUP_ALERT_ALL, + /* childAlert */ Notification.GROUP_ALERT_SUMMARY, + /* siblingAlert */ Notification.GROUP_ALERT_SUMMARY, + /* expectAlertOverride */ true); + } + + @Test + public void testAlertOverrideWithParentAlertChild() { + // Tests that if the summary alerts CHILDREN, there's no alertOverride + helpTestAlertOverride( + /* numSiblings */ 1, + /* summaryAlert */ Notification.GROUP_ALERT_CHILDREN, + /* childAlert */ Notification.GROUP_ALERT_SUMMARY, + /* siblingAlert */ Notification.GROUP_ALERT_SUMMARY, + /* expectAlertOverride */ false); + } + + @Test + public void testAlertOverrideWithChildrenAlertAll() { + // Tests that if the children alert ALL, there's no alertOverride + helpTestAlertOverride( + /* numSiblings */ 1, + /* summaryAlert */ Notification.GROUP_ALERT_SUMMARY, + /* childAlert */ Notification.GROUP_ALERT_ALL, + /* siblingAlert */ Notification.GROUP_ALERT_ALL, + /* expectAlertOverride */ false); + } + + /** * This tests, for a group with a priority entry and the given number of siblings, that: * 1) the priority entry is identified as the alertOverride for the group * 2) the onAlertOverrideChanged method is called at that time * 3) when the priority entry is removed, these are reversed */ - private void helpTestAlertOverrideWithSiblings(int numSiblings) { - int groupAlert = Notification.GROUP_ALERT_SUMMARY; + private void helpTestAlertOverride(int numSiblings, + @Notification.GroupAlertBehavior int summaryAlert, + @Notification.GroupAlertBehavior int childAlert, + @Notification.GroupAlertBehavior int siblingAlert, + boolean expectAlertOverride) { // Create entries in an order so that the priority entry can be deemed the newest child. NotificationEntry[] siblings = new NotificationEntry[numSiblings]; for (int i = 0; i < numSiblings; i++) { - siblings[i] = mGroupTestHelper.createChildNotification(groupAlert); + siblings[i] = mGroupTestHelper.createChildNotification(siblingAlert); } - NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(groupAlert); - NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(groupAlert); + NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(childAlert); + NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(summaryAlert); // The priority entry is an important conversation. when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry))) @@ -208,6 +256,14 @@ public class NotificationGroupManagerLegacyTest extends SysuiTestCase { } mGroupManager.onEntryAdded(priorityEntry); + if (!expectAlertOverride) { + // Test expectation is that there will NOT be an alert, so verify that! + NotificationGroup summaryGroup = + mGroupManager.getGroupForSummary(summaryEntry.getSbn()); + assertNull(summaryGroup.alertOverride); + return; + } + // Verify that the summary group has the priority child as its alertOverride NotificationGroup summaryGroup = mGroupManager.getGroupForSummary(summaryEntry.getSbn()); assertEquals(priorityEntry, summaryGroup.alertOverride); 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 a8a33dabf1aa..fb8c9858be0b 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 @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.phone import android.animation.Animator +import android.os.Handler +import android.os.PowerManager import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.View @@ -28,13 +30,20 @@ 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 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.Mockito +import org.mockito.Mockito.`when` +import org.mockito.Mockito.anyLong +import org.mockito.Mockito.mockingDetails +import org.mockito.Mockito.never import org.mockito.Mockito.spy +import org.mockito.Mockito.times +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest @@ -52,13 +61,19 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { @Mock private lateinit var globalSettings: GlobalSettings @Mock - private lateinit var statusbar: StatusBar + private lateinit var statusBar: StatusBar + @Mock + private lateinit var notificationPanelViewController: NotificationPanelViewController @Mock private lateinit var lightRevealScrim: LightRevealScrim @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle @Mock private lateinit var statusBarStateController: StatusBarStateControllerImpl + @Mock + private lateinit var powerManager: PowerManager + @Mock + private lateinit var handler: Handler @Before fun setUp() { @@ -71,9 +86,24 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { dagger.Lazy<KeyguardViewMediator> { keyguardViewMediator }, keyguardStateController, dagger.Lazy<DozeParameters> { dozeParameters }, - globalSettings + globalSettings, + powerManager, + handler = handler ) - controller.initialize(statusbar, lightRevealScrim) + controller.initialize(statusBar, lightRevealScrim) + `when`(statusBar.notificationPanelViewController).thenReturn( + notificationPanelViewController) + + // Screen off does not run if the panel is expanded, so we should say it's collapsed to test + // screen off. + `when`(notificationPanelViewController.isFullyCollapsed).thenReturn(true) + } + + @After + fun cleanUp() { + // Tell the screen off controller to cancel the animations and clean up its state, or + // subsequent tests will act unpredictably as the animator continues running. + controller.onStartedWakingUp() } @Test @@ -89,4 +119,51 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { listener.value.onAnimationEnd(null) Mockito.verify(animator).setListener(null) } + + /** + * The AOD UI is shown during the screen off animation, after a delay to allow the light reveal + * animation to start. If the device is woken up during the screen off, we should *never* do + * this. + * + * This test confirms that we do show the AOD UI when the device is not woken up + * (PowerManager#isInteractive = false). + */ + @Test + fun testAodUiShownIfNotInteractive() { + `when`(dozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true) + `when`(powerManager.isInteractive).thenReturn(false) + + val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java) + controller.onStartedGoingToSleep() + + verify(handler).postDelayed(callbackCaptor.capture(), anyLong()) + + callbackCaptor.value.run() + + verify(notificationPanelViewController, times(1)).showAodUi() + } + + /** + * The AOD UI is shown during the screen off animation, after a delay to allow the light reveal + * animation to start. If the device is woken up during the screen off, we should *never* do + * this. + * + * This test confirms that we do not show the AOD UI when the device is woken up during screen + * off (PowerManager#isInteractive = true). + */ + @Test + fun testAodUiNotShownIfInteractive() { + `when`(dozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true) + `when`(powerManager.isInteractive).thenReturn(true) + + val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java) + controller.onStartedGoingToSleep() + + mockingDetails(handler).printInvocations() + + verify(handler).postDelayed(callbackCaptor.capture(), anyLong()) + callbackCaptor.value.run() + + verify(notificationPanelViewController, never()).showAodUi() + } }
\ No newline at end of file diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java index d04698cb5aeb..db5aa9db3388 100644 --- a/services/core/java/com/android/server/GestureLauncherService.java +++ b/services/core/java/com/android/server/GestureLauncherService.java @@ -83,6 +83,20 @@ public class GestureLauncherService extends SystemService { private static final int EMERGENCY_GESTURE_POWER_TAP_COUNT_THRESHOLD = 5; /** + * Default value of the power button "cooldown" period after the Emergency gesture is triggered. + * See {@link Settings.Secure#EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS} + */ + private static final int EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_DEFAULT = 3000; + + /** + * Maximum value of the power button "cooldown" period after the Emergency gesture is triggered. + * The value read from {@link Settings.Secure#EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS} + * is capped at this maximum. + */ + @VisibleForTesting + static final int EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX = 5000; + + /** * Number of taps required to launch camera shortcut. */ private static final int CAMERA_POWER_TAP_COUNT_THRESHOLD = 2; @@ -145,7 +159,14 @@ public class GestureLauncherService extends SystemService { */ private boolean mEmergencyGestureEnabled; + /** + * Power button cooldown period in milliseconds, after emergency gesture is triggered. A zero + * value means the cooldown period is disabled. + */ + private int mEmergencyGesturePowerButtonCooldownPeriodMs; + private long mLastPowerDown; + private long mLastEmergencyGestureTriggered; private int mPowerButtonConsecutiveTaps; private int mPowerButtonSlowConsecutiveTaps; private final UiEventLogger mUiEventLogger; @@ -210,6 +231,7 @@ public class GestureLauncherService extends SystemService { updateCameraRegistered(); updateCameraDoubleTapPowerEnabled(); updateEmergencyGestureEnabled(); + updateEmergencyGesturePowerButtonCooldownPeriodMs(); mUserId = ActivityManager.getCurrentUser(); mContext.registerReceiver(mUserReceiver, new IntentFilter(Intent.ACTION_USER_SWITCHED)); @@ -230,6 +252,10 @@ public class GestureLauncherService extends SystemService { mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.EMERGENCY_GESTURE_ENABLED), false, mSettingObserver, mUserId); + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor( + Settings.Secure.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS), + false, mSettingObserver, mUserId); } private void updateCameraRegistered() { @@ -263,6 +289,14 @@ public class GestureLauncherService extends SystemService { } } + @VisibleForTesting + void updateEmergencyGesturePowerButtonCooldownPeriodMs() { + int cooldownPeriodMs = getEmergencyGesturePowerButtonCooldownPeriodMs(mContext, mUserId); + synchronized (this) { + mEmergencyGesturePowerButtonCooldownPeriodMs = cooldownPeriodMs; + } + } + private void unregisterCameraLaunchGesture() { if (mCameraLaunchRegistered) { mCameraLaunchRegistered = false; @@ -398,6 +432,20 @@ public class GestureLauncherService extends SystemService { } /** + * Gets power button cooldown period in milliseconds after emergency gesture is triggered. The + * value is capped at a maximum + * {@link GestureLauncherService#EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX}. If the + * value is zero, it means the cooldown period is disabled. + */ + @VisibleForTesting + static int getEmergencyGesturePowerButtonCooldownPeriodMs(Context context, int userId) { + int cooldown = Settings.Secure.getIntForUser(context.getContentResolver(), + Settings.Secure.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS, + EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_DEFAULT, userId); + return Math.min(cooldown, EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX); + } + + /** * Whether to enable the camera launch gesture. */ private static boolean isCameraLaunchEnabled(Resources resources) { @@ -445,10 +493,24 @@ public class GestureLauncherService extends SystemService { */ public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive, MutableBoolean outLaunched) { + if (mEmergencyGestureEnabled && mEmergencyGesturePowerButtonCooldownPeriodMs >= 0 + && event.getEventTime() - mLastEmergencyGestureTriggered + < mEmergencyGesturePowerButtonCooldownPeriodMs) { + Slog.i(TAG, String.format( + "Suppressing power button: within %dms cooldown period after Emergency " + + "Gesture. Begin=%dms, end=%dms.", + mEmergencyGesturePowerButtonCooldownPeriodMs, + mLastEmergencyGestureTriggered, + mLastEmergencyGestureTriggered + mEmergencyGesturePowerButtonCooldownPeriodMs)); + outLaunched.value = false; + return true; + } + if (event.isLongPress()) { // Long presses are sent as a second key down. If the long press threshold is set lower // than the double tap of sequence interval thresholds, this could cause false double // taps or consecutive taps, so we want to ignore the long press event. + outLaunched.value = false; return false; } boolean launchCamera = false; @@ -509,6 +571,12 @@ public class GestureLauncherService extends SystemService { Slog.i(TAG, "Emergency gesture detected, launching."); launchEmergencyGesture = handleEmergencyGesture(); mUiEventLogger.log(GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER); + // Record emergency trigger time if emergency UI was launched + if (launchEmergencyGesture) { + synchronized (this) { + mLastEmergencyGestureTriggered = event.getEventTime(); + } + } } mMetricsLogger.histogram("power_consecutive_short_tap_count", mPowerButtonSlowConsecutiveTaps); @@ -600,6 +668,7 @@ public class GestureLauncherService extends SystemService { updateCameraRegistered(); updateCameraDoubleTapPowerEnabled(); updateEmergencyGestureEnabled(); + updateEmergencyGesturePowerButtonCooldownPeriodMs(); } } }; @@ -610,6 +679,7 @@ public class GestureLauncherService extends SystemService { updateCameraRegistered(); updateCameraDoubleTapPowerEnabled(); updateEmergencyGestureEnabled(); + updateEmergencyGesturePowerButtonCooldownPeriodMs(); } } }; diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index 9e8b9c62ff51..3288ca837c1f 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -23,21 +23,6 @@ "file_patterns": ["NotificationManagerService\\.java"] }, { - "name": "CtsContentTestCases", - "options": [ - { - "include-filter": "android.content.cts.ClipboardManagerTest" - }, - { - "include-filter": "android.content.cts.ClipDataTest" - }, - { - "include-filter": "android.content.cts.ClipDescriptionTest" - } - ], - "file_patterns": ["ClipboardService\\.java"] - }, - { "name": "FrameworksMockingServicesTests", "options": [ { @@ -59,6 +44,21 @@ { "name": "CtsScopedStorageDeviceOnlyTest", "file_patterns": ["StorageManagerService\\.java"] + }, + { + "name": "CtsContentTestCases", + "options": [ + { + "include-filter": "android.content.cts.ClipboardManagerTest" + }, + { + "include-filter": "android.content.cts.ClipDataTest" + }, + { + "include-filter": "android.content.cts.ClipDescriptionTest" + } + ], + "file_patterns": ["ClipboardService\\.java"] } ] } diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index b94cea4d5d40..40c8f1940bda 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -225,6 +225,9 @@ public final class PlaybackActivityMonitor AudioAttributes.FLAG_BYPASS_MUTE; private void checkVolumeForPrivilegedAlarm(AudioPlaybackConfiguration apc, int event) { + if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_DEVICE_ID) { + return; + } if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED || apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) { if ((apc.getAudioAttributes().getAllFlags() & FLAGS_FOR_SILENCE_OVERRIDE) diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java index b73e91173a43..26bbb403f39f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java +++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors; +import static com.android.internal.annotations.VisibleForTesting.Visibility; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -48,7 +50,6 @@ public abstract class BaseClientMonitor extends LoggableMonitor * Interface that ClientMonitor holders should use to receive callbacks. */ public interface Callback { - /** * Invoked when the ClientMonitor operation has been started (e.g. reached the head of * the queue and becomes the current operation). @@ -203,7 +204,8 @@ public abstract class BaseClientMonitor extends LoggableMonitor } /** Signals this operation has completed its lifecycle and should no longer be used. */ - void destroy() { + @VisibleForTesting(visibility = Visibility.PACKAGE) + public void destroy() { mAlreadyDone = true; if (mToken != null) { try { 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 a358bc2bad55..39c5944d65c7 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -17,10 +17,10 @@ package com.android.server.biometrics.sensors; import android.annotation.IntDef; +import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.IBiometricService; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Handler; @@ -55,6 +55,7 @@ import java.util.Locale; * We currently assume (and require) that each biometric sensor have its own instance of a * {@link BiometricScheduler}. See {@link CoexCoordinator}. */ +@MainThread public class BiometricScheduler { private static final String BASE_TAG = "BiometricScheduler"; @@ -110,123 +111,6 @@ public class BiometricScheduler { } } - /** - * Contains all the necessary information for a HAL operation. - */ - @VisibleForTesting - static final class Operation { - - /** - * The operation is added to the list of pending operations and waiting for its turn. - */ - static final int STATE_WAITING_IN_QUEUE = 0; - - /** - * The operation is added to the list of pending operations, but a subsequent operation - * has been added. This state only applies to {@link Interruptable} operations. When this - * operation reaches the head of the queue, it will send ERROR_CANCELED and finish. - */ - static final int STATE_WAITING_IN_QUEUE_CANCELING = 1; - - /** - * The operation has reached the front of the queue and has started. - */ - static final int STATE_STARTED = 2; - - /** - * The operation was started, but is now canceling. Operations should wait for the HAL to - * acknowledge that the operation was canceled, at which point it finishes. - */ - static final int STATE_STARTED_CANCELING = 3; - - /** - * The operation has reached the head of the queue but is waiting for BiometricService - * to acknowledge and start the operation. - */ - static final int STATE_WAITING_FOR_COOKIE = 4; - - /** - * The {@link BaseClientMonitor.Callback} has been invoked and the client is finished. - */ - static final int STATE_FINISHED = 5; - - @IntDef({STATE_WAITING_IN_QUEUE, - STATE_WAITING_IN_QUEUE_CANCELING, - STATE_STARTED, - STATE_STARTED_CANCELING, - STATE_WAITING_FOR_COOKIE, - STATE_FINISHED}) - @Retention(RetentionPolicy.SOURCE) - @interface OperationState {} - - @NonNull final BaseClientMonitor mClientMonitor; - @Nullable final BaseClientMonitor.Callback mClientCallback; - @OperationState int mState; - - Operation( - @NonNull BaseClientMonitor clientMonitor, - @Nullable BaseClientMonitor.Callback callback - ) { - this(clientMonitor, callback, STATE_WAITING_IN_QUEUE); - } - - protected Operation( - @NonNull BaseClientMonitor clientMonitor, - @Nullable BaseClientMonitor.Callback callback, - @OperationState int state - ) { - mClientMonitor = clientMonitor; - mClientCallback = callback; - mState = state; - } - - public boolean isHalOperation() { - return mClientMonitor instanceof HalClientMonitor<?>; - } - - /** - * @return true if the operation requires the HAL, and the HAL is null. - */ - public boolean isUnstartableHalOperation() { - if (isHalOperation()) { - final HalClientMonitor<?> client = (HalClientMonitor<?>) mClientMonitor; - if (client.getFreshDaemon() == null) { - return true; - } - } - return false; - } - - @Override - public String toString() { - return mClientMonitor + ", State: " + mState; - } - } - - /** - * Monitors an operation's cancellation. If cancellation takes too long, the watchdog will - * kill the current operation and forcibly start the next. - */ - private static final class CancellationWatchdog implements Runnable { - static final int DELAY_MS = 3000; - - final String tag; - final Operation operation; - CancellationWatchdog(String tag, Operation operation) { - this.tag = tag; - this.operation = operation; - } - - @Override - public void run() { - if (operation.mState != Operation.STATE_FINISHED) { - Slog.e(tag, "[Watchdog Triggered]: " + operation); - operation.mClientMonitor.mCallback - .onClientFinished(operation.mClientMonitor, false /* success */); - } - } - } - private static final class CrashState { static final int NUM_ENTRIES = 10; final String timestamp; @@ -263,10 +147,9 @@ public class BiometricScheduler { private final @SensorType int mSensorType; @Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher; @NonNull private final IBiometricService mBiometricService; - @NonNull protected final Handler mHandler = new Handler(Looper.getMainLooper()); - @NonNull private final InternalCallback mInternalCallback; - @VisibleForTesting @NonNull final Deque<Operation> mPendingOperations; - @VisibleForTesting @Nullable Operation mCurrentOperation; + @NonNull protected final Handler mHandler; + @VisibleForTesting @NonNull final Deque<BiometricSchedulerOperation> mPendingOperations; + @VisibleForTesting @Nullable BiometricSchedulerOperation mCurrentOperation; @NonNull private final ArrayDeque<CrashState> mCrashStates; private int mTotalOperationsHandled; @@ -277,7 +160,7 @@ public class BiometricScheduler { // Internal callback, notified when an operation is complete. Notifies the requester // that the operation is complete, before performing internal scheduler work (such as // starting the next client). - public class InternalCallback implements BaseClientMonitor.Callback { + private final BaseClientMonitor.Callback mInternalCallback = new BaseClientMonitor.Callback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { Slog.d(getTag(), "[Started] " + clientMonitor); @@ -286,16 +169,11 @@ public class BiometricScheduler { mCoexCoordinator.addAuthenticationClient(mSensorType, (AuthenticationClient<?>) clientMonitor); } - - if (mCurrentOperation.mClientCallback != null) { - mCurrentOperation.mClientCallback.onClientStarted(clientMonitor); - } } @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { mHandler.post(() -> { - clientMonitor.destroy(); if (mCurrentOperation == null) { Slog.e(getTag(), "[Finishing] " + clientMonitor + " but current operation is null, success: " + success @@ -303,9 +181,9 @@ public class BiometricScheduler { return; } - if (clientMonitor != mCurrentOperation.mClientMonitor) { + if (!mCurrentOperation.isFor(clientMonitor)) { Slog.e(getTag(), "[Ignoring Finish] " + clientMonitor + " does not match" - + " current: " + mCurrentOperation.mClientMonitor); + + " current: " + mCurrentOperation); return; } @@ -315,36 +193,33 @@ public class BiometricScheduler { (AuthenticationClient<?>) clientMonitor); } - mCurrentOperation.mState = Operation.STATE_FINISHED; - - if (mCurrentOperation.mClientCallback != null) { - mCurrentOperation.mClientCallback.onClientFinished(clientMonitor, success); - } - if (mGestureAvailabilityDispatcher != null) { mGestureAvailabilityDispatcher.markSensorActive( - mCurrentOperation.mClientMonitor.getSensorId(), false /* active */); + mCurrentOperation.getSensorId(), false /* active */); } if (mRecentOperations.size() >= mRecentOperationsLimit) { mRecentOperations.remove(0); } - mRecentOperations.add(mCurrentOperation.mClientMonitor.getProtoEnum()); + mRecentOperations.add(mCurrentOperation.getProtoEnum()); mCurrentOperation = null; mTotalOperationsHandled++; startNextOperationIfIdle(); }); } - } + }; @VisibleForTesting - BiometricScheduler(@NonNull String tag, @SensorType int sensorType, + BiometricScheduler(@NonNull String tag, + @NonNull Handler handler, + @SensorType int sensorType, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher, - @NonNull IBiometricService biometricService, int recentOperationsLimit, + @NonNull IBiometricService biometricService, + int recentOperationsLimit, @NonNull CoexCoordinator coexCoordinator) { mBiometricTag = tag; + mHandler = handler; mSensorType = sensorType; - mInternalCallback = new InternalCallback(); mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher; mPendingOperations = new ArrayDeque<>(); mBiometricService = biometricService; @@ -356,6 +231,7 @@ public class BiometricScheduler { /** * Creates a new scheduler. + * * @param tag for the specific instance of the scheduler. Should be unique. * @param sensorType the sensorType that this scheduler is handling. * @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures @@ -364,16 +240,14 @@ public class BiometricScheduler { public BiometricScheduler(@NonNull String tag, @SensorType int sensorType, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { - this(tag, sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface( - ServiceManager.getService(Context.BIOMETRIC_SERVICE)), LOG_NUM_RECENT_OPERATIONS, - CoexCoordinator.getInstance()); + this(tag, new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher, + IBiometricService.Stub.asInterface( + ServiceManager.getService(Context.BIOMETRIC_SERVICE)), + LOG_NUM_RECENT_OPERATIONS, CoexCoordinator.getInstance()); } - /** - * @return A reference to the internal callback that should be invoked whenever the scheduler - * needs to (e.g. client started, client finished). - */ - @NonNull protected InternalCallback getInternalCallback() { + @VisibleForTesting + public BaseClientMonitor.Callback getInternalCallback() { return mInternalCallback; } @@ -392,72 +266,46 @@ public class BiometricScheduler { } mCurrentOperation = mPendingOperations.poll(); - final BaseClientMonitor currentClient = mCurrentOperation.mClientMonitor; Slog.d(getTag(), "[Polled] " + mCurrentOperation); // If the operation at the front of the queue has been marked for cancellation, send // ERROR_CANCELED. No need to start this client. - if (mCurrentOperation.mState == Operation.STATE_WAITING_IN_QUEUE_CANCELING) { + if (mCurrentOperation.isMarkedCanceling()) { Slog.d(getTag(), "[Now Cancelling] " + mCurrentOperation); - if (!(currentClient instanceof Interruptable)) { - throw new IllegalStateException("Mis-implemented client or scheduler, " - + "trying to cancel non-interruptable operation: " + mCurrentOperation); - } - - final Interruptable interruptable = (Interruptable) currentClient; - interruptable.cancelWithoutStarting(getInternalCallback()); + mCurrentOperation.cancel(mHandler, mInternalCallback); // Now we wait for the client to send its FinishCallback, which kicks off the next // operation. return; } - if (mGestureAvailabilityDispatcher != null - && mCurrentOperation.mClientMonitor instanceof AcquisitionClient) { + if (mGestureAvailabilityDispatcher != null && mCurrentOperation.isAcquisitionOperation()) { mGestureAvailabilityDispatcher.markSensorActive( - mCurrentOperation.mClientMonitor.getSensorId(), - true /* active */); + mCurrentOperation.getSensorId(), true /* active */); } // Not all operations start immediately. BiometricPrompt waits for its operation // to arrive at the head of the queue, before pinging it to start. - final boolean shouldStartNow = currentClient.getCookie() == 0; - if (shouldStartNow) { - if (mCurrentOperation.isUnstartableHalOperation()) { - final HalClientMonitor<?> halClientMonitor = - (HalClientMonitor<?>) mCurrentOperation.mClientMonitor; + final int cookie = mCurrentOperation.isReadyToStart(); + if (cookie == 0) { + if (!mCurrentOperation.start(mInternalCallback)) { // Note down current length of queue final int pendingOperationsLength = mPendingOperations.size(); - final Operation lastOperation = mPendingOperations.peekLast(); + final BiometricSchedulerOperation lastOperation = mPendingOperations.peekLast(); Slog.e(getTag(), "[Unable To Start] " + mCurrentOperation + ". Last pending operation: " + lastOperation); - // For current operations, 1) unableToStart, which notifies the caller-side, then - // 2) notify operation's callback, to notify applicable system service that the - // operation failed. - halClientMonitor.unableToStart(); - if (mCurrentOperation.mClientCallback != null) { - mCurrentOperation.mClientCallback.onClientFinished( - mCurrentOperation.mClientMonitor, false /* success */); - } - // Then for each operation currently in the pending queue at the time of this // failure, do the same as above. Otherwise, it's possible that something like // setActiveUser fails, but then authenticate (for the wrong user) is invoked. for (int i = 0; i < pendingOperationsLength; i++) { - final Operation operation = mPendingOperations.pollFirst(); - if (operation == null) { + final BiometricSchedulerOperation operation = mPendingOperations.pollFirst(); + if (operation != null) { + Slog.w(getTag(), "[Aborting Operation] " + operation); + operation.abort(); + } else { Slog.e(getTag(), "Null operation, index: " + i + ", expected length: " + pendingOperationsLength); - break; - } - if (operation.isHalOperation()) { - ((HalClientMonitor<?>) operation.mClientMonitor).unableToStart(); - } - if (operation.mClientCallback != null) { - operation.mClientCallback.onClientFinished(operation.mClientMonitor, - false /* success */); } - Slog.w(getTag(), "[Aborted Operation] " + operation); } // It's possible that during cleanup a new set of operations came in. We can try to @@ -465,25 +313,20 @@ public class BiometricScheduler { // actually be multiple operations (i.e. updateActiveUser + authenticate). mCurrentOperation = null; startNextOperationIfIdle(); - } else { - Slog.d(getTag(), "[Starting] " + mCurrentOperation); - currentClient.start(getInternalCallback()); - mCurrentOperation.mState = Operation.STATE_STARTED; } } else { try { - mBiometricService.onReadyForAuthentication(currentClient.getCookie()); + mBiometricService.onReadyForAuthentication(cookie); } catch (RemoteException e) { Slog.e(getTag(), "Remote exception when contacting BiometricService", e); } Slog.d(getTag(), "Waiting for cookie before starting: " + mCurrentOperation); - mCurrentOperation.mState = Operation.STATE_WAITING_FOR_COOKIE; } } /** * Starts the {@link #mCurrentOperation} if - * 1) its state is {@link Operation#STATE_WAITING_FOR_COOKIE} and + * 1) its state is {@link BiometricSchedulerOperation#STATE_WAITING_FOR_COOKIE} and * 2) its cookie matches this cookie * * This is currently only used by {@link com.android.server.biometrics.BiometricService}, which @@ -499,45 +342,13 @@ public class BiometricScheduler { Slog.e(getTag(), "Current operation is null"); return; } - if (mCurrentOperation.mState != Operation.STATE_WAITING_FOR_COOKIE) { - if (mCurrentOperation.mState == Operation.STATE_WAITING_IN_QUEUE_CANCELING) { - Slog.d(getTag(), "Operation was marked for cancellation, cancelling now: " - + mCurrentOperation); - // This should trigger the internal onClientFinished callback, which clears the - // operation and starts the next one. - final ErrorConsumer errorConsumer = - (ErrorConsumer) mCurrentOperation.mClientMonitor; - errorConsumer.onError(BiometricConstants.BIOMETRIC_ERROR_CANCELED, - 0 /* vendorCode */); - return; - } else { - Slog.e(getTag(), "Operation is in the wrong state: " + mCurrentOperation - + ", expected STATE_WAITING_FOR_COOKIE"); - return; - } - } - if (mCurrentOperation.mClientMonitor.getCookie() != cookie) { - Slog.e(getTag(), "Mismatched cookie for operation: " + mCurrentOperation - + ", received: " + cookie); - return; - } - if (mCurrentOperation.isUnstartableHalOperation()) { + if (mCurrentOperation.startWithCookie(mInternalCallback, cookie)) { + Slog.d(getTag(), "[Started] Prepared client: " + mCurrentOperation); + } else { Slog.e(getTag(), "[Unable To Start] Prepared client: " + mCurrentOperation); - // This is BiometricPrompt trying to auth but something's wrong with the HAL. - final HalClientMonitor<?> halClientMonitor = - (HalClientMonitor<?>) mCurrentOperation.mClientMonitor; - halClientMonitor.unableToStart(); - if (mCurrentOperation.mClientCallback != null) { - mCurrentOperation.mClientCallback.onClientFinished(mCurrentOperation.mClientMonitor, - false /* success */); - } mCurrentOperation = null; startNextOperationIfIdle(); - } else { - Slog.d(getTag(), "[Starting] Prepared client: " + mCurrentOperation); - mCurrentOperation.mState = Operation.STATE_STARTED; - mCurrentOperation.mClientMonitor.start(getInternalCallback()); } } @@ -562,17 +373,14 @@ public class BiometricScheduler { // pending clients as canceling. Once they reach the head of the queue, the scheduler will // send ERROR_CANCELED and skip the operation. if (clientMonitor.interruptsPrecedingClients()) { - for (Operation operation : mPendingOperations) { - if (operation.mClientMonitor instanceof Interruptable - && operation.mState != Operation.STATE_WAITING_IN_QUEUE_CANCELING) { - Slog.d(getTag(), "New client incoming, marking pending client as canceling: " - + operation.mClientMonitor); - operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING; + for (BiometricSchedulerOperation operation : mPendingOperations) { + if (operation.markCanceling()) { + Slog.d(getTag(), "New client, marking pending op as canceling: " + operation); } } } - mPendingOperations.add(new Operation(clientMonitor, clientCallback)); + mPendingOperations.add(new BiometricSchedulerOperation(clientMonitor, clientCallback)); Slog.d(getTag(), "[Added] " + clientMonitor + ", new queue size: " + mPendingOperations.size()); @@ -580,67 +388,34 @@ public class BiometricScheduler { // cancellable, start the cancellation process. if (clientMonitor.interruptsPrecedingClients() && mCurrentOperation != null - && mCurrentOperation.mClientMonitor instanceof Interruptable - && mCurrentOperation.mState == Operation.STATE_STARTED) { + && mCurrentOperation.isInterruptable() + && mCurrentOperation.isStarted()) { Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation); - cancelInternal(mCurrentOperation); - } - - startNextOperationIfIdle(); - } - - private void cancelInternal(Operation operation) { - if (operation != mCurrentOperation) { - Slog.e(getTag(), "cancelInternal invoked on non-current operation: " + operation); - return; - } - if (!(operation.mClientMonitor instanceof Interruptable)) { - Slog.w(getTag(), "Operation not interruptable: " + operation); - return; - } - if (operation.mState == Operation.STATE_STARTED_CANCELING) { - Slog.w(getTag(), "Cancel already invoked for operation: " + operation); - return; - } - if (operation.mState == Operation.STATE_WAITING_FOR_COOKIE) { - Slog.w(getTag(), "Skipping cancellation for non-started operation: " + operation); - // We can set it to null immediately, since the HAL was never notified to start. - if (mCurrentOperation != null) { - mCurrentOperation.mClientMonitor.destroy(); - } - mCurrentOperation = null; + mCurrentOperation.cancel(mHandler, mInternalCallback); + } else { startNextOperationIfIdle(); - return; } - Slog.d(getTag(), "[Cancelling] Current client: " + operation.mClientMonitor); - final Interruptable interruptable = (Interruptable) operation.mClientMonitor; - interruptable.cancel(); - operation.mState = Operation.STATE_STARTED_CANCELING; - - // Add a watchdog. If the HAL does not acknowledge within the timeout, we will - // forcibly finish this client. - mHandler.postDelayed(new CancellationWatchdog(getTag(), operation), - CancellationWatchdog.DELAY_MS); } /** * Requests to cancel enrollment. * @param token from the caller, should match the token passed in when requesting enrollment */ - public void cancelEnrollment(IBinder token) { - if (mCurrentOperation == null) { - Slog.e(getTag(), "Unable to cancel enrollment, null operation"); - return; - } - final boolean isEnrolling = mCurrentOperation.mClientMonitor instanceof EnrollClient; - final boolean tokenMatches = mCurrentOperation.mClientMonitor.getToken() == token; - if (!isEnrolling || !tokenMatches) { - Slog.w(getTag(), "Not cancelling enrollment, isEnrolling: " + isEnrolling - + " tokenMatches: " + tokenMatches); - return; - } + public void cancelEnrollment(IBinder token, long requestId) { + Slog.d(getTag(), "cancelEnrollment, requestId: " + requestId); - cancelInternal(mCurrentOperation); + if (mCurrentOperation != null + && canCancelEnrollOperation(mCurrentOperation, token, requestId)) { + Slog.d(getTag(), "Cancelling enrollment op: " + mCurrentOperation); + mCurrentOperation.cancel(mHandler, mInternalCallback); + } else { + for (BiometricSchedulerOperation operation : mPendingOperations) { + if (canCancelEnrollOperation(operation, token, requestId)) { + Slog.d(getTag(), "Cancelling pending enrollment op: " + operation); + operation.markCanceling(); + } + } + } } /** @@ -649,62 +424,42 @@ public class BiometricScheduler { * @param requestId the id returned when requesting authentication */ public void cancelAuthenticationOrDetection(IBinder token, long requestId) { - Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId - + " current: " + mCurrentOperation - + " stack size: " + mPendingOperations.size()); + Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId); if (mCurrentOperation != null && canCancelAuthOperation(mCurrentOperation, token, requestId)) { - Slog.d(getTag(), "Cancelling: " + mCurrentOperation); - cancelInternal(mCurrentOperation); + Slog.d(getTag(), "Cancelling auth/detect op: " + mCurrentOperation); + mCurrentOperation.cancel(mHandler, mInternalCallback); } else { - // Look through the current queue for all authentication clients for the specified - // token, and mark them as STATE_WAITING_IN_QUEUE_CANCELING. Note that we're marking - // all of them, instead of just the first one, since the API surface currently doesn't - // allow us to distinguish between multiple authentication requests from the same - // process. However, this generally does not happen anyway, and would be a class of - // bugs on its own. - for (Operation operation : mPendingOperations) { + for (BiometricSchedulerOperation operation : mPendingOperations) { if (canCancelAuthOperation(operation, token, requestId)) { - Slog.d(getTag(), "Marking " + operation - + " as STATE_WAITING_IN_QUEUE_CANCELING"); - operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING; + Slog.d(getTag(), "Cancelling pending auth/detect op: " + operation); + operation.markCanceling(); } } } } - private static boolean canCancelAuthOperation(Operation operation, IBinder token, - long requestId) { - // TODO: restrict callers that can cancel without requestId (negative value)? - return isAuthenticationOrDetectionOperation(operation) - && operation.mClientMonitor.getToken() == token - && isMatchingRequestId(operation, requestId); - } - - // By default, monitors are not associated with a request id to retain the original - // behavior (i.e. if no requestId is explicitly set then assume it matches) - private static boolean isMatchingRequestId(Operation operation, long requestId) { - return !operation.mClientMonitor.hasRequestId() - || operation.mClientMonitor.getRequestId() == requestId; + private static boolean canCancelEnrollOperation(BiometricSchedulerOperation operation, + IBinder token, long requestId) { + return operation.isEnrollOperation() + && operation.isMatchingToken(token) + && operation.isMatchingRequestId(requestId); } - private static boolean isAuthenticationOrDetectionOperation(@NonNull Operation operation) { - final boolean isAuthentication = - operation.mClientMonitor instanceof AuthenticationConsumer; - final boolean isDetection = - operation.mClientMonitor instanceof DetectionConsumer; - return isAuthentication || isDetection; + private static boolean canCancelAuthOperation(BiometricSchedulerOperation operation, + IBinder token, long requestId) { + // TODO: restrict callers that can cancel without requestId (negative value)? + return operation.isAuthenticationOrDetectionOperation() + && operation.isMatchingToken(token) + && operation.isMatchingRequestId(requestId); } /** * @return the current operation */ public BaseClientMonitor getCurrentClient() { - if (mCurrentOperation == null) { - return null; - } - return mCurrentOperation.mClientMonitor; + return mCurrentOperation != null ? mCurrentOperation.getClientMonitor() : null; } public int getCurrentPendingCount() { @@ -719,7 +474,7 @@ public class BiometricScheduler { new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US); final String timestamp = dateFormat.format(new Date(System.currentTimeMillis())); final List<String> pendingOperations = new ArrayList<>(); - for (Operation operation : mPendingOperations) { + for (BiometricSchedulerOperation operation : mPendingOperations) { pendingOperations.add(operation.toString()); } @@ -735,7 +490,7 @@ public class BiometricScheduler { pw.println("Type: " + mSensorType); pw.println("Current operation: " + mCurrentOperation); pw.println("Pending operations: " + mPendingOperations.size()); - for (Operation operation : mPendingOperations) { + for (BiometricSchedulerOperation operation : mPendingOperations) { pw.println("Pending operation: " + operation); } for (CrashState crashState : mCrashStates) { @@ -746,7 +501,7 @@ public class BiometricScheduler { public byte[] dumpProtoState(boolean clearSchedulerBuffer) { final ProtoOutputStream proto = new ProtoOutputStream(); proto.write(BiometricSchedulerProto.CURRENT_OPERATION, mCurrentOperation != null - ? mCurrentOperation.mClientMonitor.getProtoEnum() : BiometricsProto.CM_NONE); + ? mCurrentOperation.getProtoEnum() : BiometricsProto.CM_NONE); proto.write(BiometricSchedulerProto.TOTAL_OPERATIONS, mTotalOperationsHandled); if (!mRecentOperations.isEmpty()) { @@ -771,6 +526,7 @@ public class BiometricScheduler { * HAL dies. */ public void reset() { + Slog.d(getTag(), "Resetting scheduler"); mPendingOperations.clear(); mCurrentOperation = null; } diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java new file mode 100644 index 000000000000..e8b50d90b586 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java @@ -0,0 +1,421 @@ +/* + * 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.server.biometrics.sensors; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.hardware.biometrics.BiometricConstants; +import android.os.Handler; +import android.os.IBinder; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Contains all the necessary information for a HAL operation. + */ +public class BiometricSchedulerOperation { + protected static final String TAG = "BiometricSchedulerOperation"; + + /** + * The operation is added to the list of pending operations and waiting for its turn. + */ + protected static final int STATE_WAITING_IN_QUEUE = 0; + + /** + * The operation is added to the list of pending operations, but a subsequent operation + * has been added. This state only applies to {@link Interruptable} operations. When this + * operation reaches the head of the queue, it will send ERROR_CANCELED and finish. + */ + protected static final int STATE_WAITING_IN_QUEUE_CANCELING = 1; + + /** + * The operation has reached the front of the queue and has started. + */ + protected static final int STATE_STARTED = 2; + + /** + * The operation was started, but is now canceling. Operations should wait for the HAL to + * acknowledge that the operation was canceled, at which point it finishes. + */ + protected static final int STATE_STARTED_CANCELING = 3; + + /** + * The operation has reached the head of the queue but is waiting for BiometricService + * to acknowledge and start the operation. + */ + protected static final int STATE_WAITING_FOR_COOKIE = 4; + + /** + * The {@link BaseClientMonitor.Callback} has been invoked and the client is finished. + */ + protected static final int STATE_FINISHED = 5; + + @IntDef({STATE_WAITING_IN_QUEUE, + STATE_WAITING_IN_QUEUE_CANCELING, + STATE_STARTED, + STATE_STARTED_CANCELING, + STATE_WAITING_FOR_COOKIE, + STATE_FINISHED}) + @Retention(RetentionPolicy.SOURCE) + protected @interface OperationState {} + + private static final int CANCEL_WATCHDOG_DELAY_MS = 3000; + + @NonNull + private final BaseClientMonitor mClientMonitor; + @Nullable + private final BaseClientMonitor.Callback mClientCallback; + @OperationState + private int mState; + @VisibleForTesting + @NonNull + final Runnable mCancelWatchdog; + + BiometricSchedulerOperation( + @NonNull BaseClientMonitor clientMonitor, + @Nullable BaseClientMonitor.Callback callback + ) { + this(clientMonitor, callback, STATE_WAITING_IN_QUEUE); + } + + protected BiometricSchedulerOperation( + @NonNull BaseClientMonitor clientMonitor, + @Nullable BaseClientMonitor.Callback callback, + @OperationState int state + ) { + mClientMonitor = clientMonitor; + mClientCallback = callback; + mState = state; + mCancelWatchdog = () -> { + if (!isFinished()) { + Slog.e(TAG, "[Watchdog Triggered]: " + this); + getWrappedCallback().onClientFinished(mClientMonitor, false /* success */); + } + }; + } + + /** + * Zero if this operation is ready to start or has already started. A non-zero cookie + * is returned if the operation has not started and is waiting on + * {@link android.hardware.biometrics.IBiometricService#onReadyForAuthentication(int)}. + * + * @return cookie or 0 if ready/started + */ + public int isReadyToStart() { + if (mState == STATE_WAITING_FOR_COOKIE || mState == STATE_WAITING_IN_QUEUE) { + final int cookie = mClientMonitor.getCookie(); + if (cookie != 0) { + mState = STATE_WAITING_FOR_COOKIE; + } + return cookie; + } + + return 0; + } + + /** + * Start this operation without waiting for a cookie + * (i.e. {@link #isReadyToStart() returns zero} + * + * @param callback lifecycle callback + * @return if this operation started + */ + public boolean start(@NonNull BaseClientMonitor.Callback callback) { + checkInState("start", + STATE_WAITING_IN_QUEUE, + STATE_WAITING_FOR_COOKIE, + STATE_WAITING_IN_QUEUE_CANCELING); + + if (mClientMonitor.getCookie() != 0) { + throw new IllegalStateException("operation requires cookie"); + } + + return doStart(callback); + } + + /** + * Start this operation after receiving the given cookie. + * + * @param callback lifecycle callback + * @param cookie cookie indicting the operation should begin + * @return if this operation started + */ + public boolean startWithCookie(@NonNull BaseClientMonitor.Callback callback, int cookie) { + checkInState("start", + STATE_WAITING_IN_QUEUE, + STATE_WAITING_FOR_COOKIE, + STATE_WAITING_IN_QUEUE_CANCELING); + + if (mClientMonitor.getCookie() != cookie) { + Slog.e(TAG, "Mismatched cookie for operation: " + this + ", received: " + cookie); + return false; + } + + return doStart(callback); + } + + private boolean doStart(@NonNull BaseClientMonitor.Callback callback) { + final BaseClientMonitor.Callback cb = getWrappedCallback(callback); + + if (mState == STATE_WAITING_IN_QUEUE_CANCELING) { + Slog.d(TAG, "Operation marked for cancellation, cancelling now: " + this); + + cb.onClientFinished(mClientMonitor, true /* success */); + if (mClientMonitor instanceof ErrorConsumer) { + final ErrorConsumer errorConsumer = (ErrorConsumer) mClientMonitor; + errorConsumer.onError(BiometricConstants.BIOMETRIC_ERROR_CANCELED, + 0 /* vendorCode */); + } else { + Slog.w(TAG, "monitor cancelled but does not implement ErrorConsumer"); + } + + return false; + } + + if (isUnstartableHalOperation()) { + Slog.v(TAG, "unable to start: " + this); + ((HalClientMonitor<?>) mClientMonitor).unableToStart(); + cb.onClientFinished(mClientMonitor, false /* success */); + return false; + } + + mState = STATE_STARTED; + mClientMonitor.start(cb); + + Slog.v(TAG, "started: " + this); + return true; + } + + /** + * Abort a pending operation. + * + * This is similar to cancel but the operation must not have been started. It will + * immediately abort the operation and notify the client that it has finished unsuccessfully. + */ + public void abort() { + checkInState("cannot abort a non-pending operation", + STATE_WAITING_IN_QUEUE, + STATE_WAITING_FOR_COOKIE, + STATE_WAITING_IN_QUEUE_CANCELING); + + if (isHalOperation()) { + ((HalClientMonitor<?>) mClientMonitor).unableToStart(); + } + getWrappedCallback().onClientFinished(mClientMonitor, false /* success */); + + Slog.v(TAG, "Aborted: " + this); + } + + /** Flags this operation as canceled, if possible, but does not cancel it until started. */ + public boolean markCanceling() { + if (mState == STATE_WAITING_IN_QUEUE && isInterruptable()) { + mState = STATE_WAITING_IN_QUEUE_CANCELING; + return true; + } + return false; + } + + /** + * Cancel the operation now. + * + * @param handler handler to use for the cancellation watchdog + * @param callback lifecycle callback (only used if this operation hasn't started, otherwise + * the callback used from {@link #start(BaseClientMonitor.Callback)} is used) + */ + public void cancel(@NonNull Handler handler, @NonNull BaseClientMonitor.Callback callback) { + checkNotInState("cancel", STATE_FINISHED); + + final int currentState = mState; + if (!isInterruptable()) { + Slog.w(TAG, "Cannot cancel - operation not interruptable: " + this); + return; + } + if (currentState == STATE_STARTED_CANCELING) { + Slog.w(TAG, "Cannot cancel - already invoked for operation: " + this); + return; + } + + mState = STATE_STARTED_CANCELING; + if (currentState == STATE_WAITING_IN_QUEUE + || currentState == STATE_WAITING_IN_QUEUE_CANCELING + || currentState == STATE_WAITING_FOR_COOKIE) { + Slog.d(TAG, "[Cancelling] Current client (without start): " + mClientMonitor); + ((Interruptable) mClientMonitor).cancelWithoutStarting(getWrappedCallback(callback)); + } else { + Slog.d(TAG, "[Cancelling] Current client: " + mClientMonitor); + ((Interruptable) mClientMonitor).cancel(); + } + + // forcibly finish this client if the HAL does not acknowledge within the timeout + handler.postDelayed(mCancelWatchdog, CANCEL_WATCHDOG_DELAY_MS); + } + + @NonNull + private BaseClientMonitor.Callback getWrappedCallback() { + return getWrappedCallback(null); + } + + @NonNull + private BaseClientMonitor.Callback getWrappedCallback( + @Nullable BaseClientMonitor.Callback callback) { + final BaseClientMonitor.Callback destroyCallback = new BaseClientMonitor.Callback() { + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + Slog.d(TAG, "[Finished / destroy]: " + clientMonitor); + mClientMonitor.destroy(); + mState = STATE_FINISHED; + } + }; + return new BaseClientMonitor.CompositeCallback(destroyCallback, callback, mClientCallback); + } + + /** {@link BaseClientMonitor#getSensorId()}. */ + public int getSensorId() { + return mClientMonitor.getSensorId(); + } + + /** {@link BaseClientMonitor#getProtoEnum()}. */ + public int getProtoEnum() { + return mClientMonitor.getProtoEnum(); + } + + /** {@link BaseClientMonitor#getTargetUserId()}. */ + public int getTargetUserId() { + return mClientMonitor.getTargetUserId(); + } + + /** If the given clientMonitor is the same as the one in the constructor. */ + public boolean isFor(@NonNull BaseClientMonitor clientMonitor) { + return mClientMonitor == clientMonitor; + } + + /** If this operation is {@link Interruptable}. */ + public boolean isInterruptable() { + return mClientMonitor instanceof Interruptable; + } + + private boolean isHalOperation() { + return mClientMonitor instanceof HalClientMonitor<?>; + } + + private boolean isUnstartableHalOperation() { + if (isHalOperation()) { + final HalClientMonitor<?> client = (HalClientMonitor<?>) mClientMonitor; + if (client.getFreshDaemon() == null) { + return true; + } + } + return false; + } + + /** If this operation is an enrollment. */ + public boolean isEnrollOperation() { + return mClientMonitor instanceof EnrollClient; + } + + /** If this operation is authentication. */ + public boolean isAuthenticateOperation() { + return mClientMonitor instanceof AuthenticationClient; + } + + /** If this operation is authentication or detection. */ + public boolean isAuthenticationOrDetectionOperation() { + final boolean isAuthentication = mClientMonitor instanceof AuthenticationConsumer; + final boolean isDetection = mClientMonitor instanceof DetectionConsumer; + return isAuthentication || isDetection; + } + + /** If this operation performs acquisition {@link AcquisitionClient}. */ + public boolean isAcquisitionOperation() { + return mClientMonitor instanceof AcquisitionClient; + } + + /** + * If this operation matches the original requestId. + * + * By default, monitors are not associated with a request id to retain the original + * behavior (i.e. if no requestId is explicitly set then assume it matches) + * + * @param requestId a unique id {@link BaseClientMonitor#setRequestId(long)}. + */ + public boolean isMatchingRequestId(long requestId) { + return !mClientMonitor.hasRequestId() + || mClientMonitor.getRequestId() == requestId; + } + + /** If the token matches */ + public boolean isMatchingToken(@Nullable IBinder token) { + return mClientMonitor.getToken() == token; + } + + /** If this operation has started. */ + public boolean isStarted() { + return mState == STATE_STARTED; + } + + /** If this operation is cancelling but has not yet completed. */ + public boolean isCanceling() { + return mState == STATE_STARTED_CANCELING; + } + + /** If this operation has finished and completed its lifecycle. */ + public boolean isFinished() { + return mState == STATE_FINISHED; + } + + /** If {@link #markCanceling()} was called but the operation hasn't been canceled. */ + public boolean isMarkedCanceling() { + return mState == STATE_WAITING_IN_QUEUE_CANCELING; + } + + /** + * The monitor passed to the constructor. + * @deprecated avoid using and move to encapsulate within the operation + */ + @Deprecated + public BaseClientMonitor getClientMonitor() { + return mClientMonitor; + } + + private void checkNotInState(String message, @OperationState int... states) { + for (int state : states) { + if (mState == state) { + throw new IllegalStateException(message + ": illegal state= " + state); + } + } + } + + private void checkInState(String message, @OperationState int... states) { + for (int state : states) { + if (mState == state) { + return; + } + } + throw new IllegalStateException(message + ": illegal state= " + mState); + } + + @Override + public String toString() { + return mClientMonitor + ", State: " + mState; + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java b/services/core/java/com/android/server/biometrics/sensors/Interruptable.java index fab98b6581a3..d5093c756415 100644 --- a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java +++ b/services/core/java/com/android/server/biometrics/sensors/Interruptable.java @@ -32,6 +32,11 @@ public interface Interruptable { * {@link BaseClientMonitor#start(BaseClientMonitor.Callback)} was invoked. This usually happens * if the client is still waiting in the pending queue and got notified that a subsequent * operation is preempting it. + * + * This method must invoke + * {@link BaseClientMonitor.Callback#onClientFinished(BaseClientMonitor, boolean)} on the + * given callback (with success). + * * @param callback invoked when the operation is completed. */ void cancelWithoutStarting(@NonNull BaseClientMonitor.Callback callback); diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java index b056bf897b5c..603cc22968a9 100644 --- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java @@ -16,10 +16,14 @@ package com.android.server.biometrics.sensors; +import static com.android.server.biometrics.sensors.BiometricSchedulerOperation.STATE_STARTED; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.IBiometricService; +import android.os.Handler; +import android.os.Looper; import android.os.ServiceManager; import android.os.UserHandle; import android.util.Slog; @@ -68,9 +72,8 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { return; } - Slog.d(getTag(), "[Client finished] " - + clientMonitor + ", success: " + success); - if (mCurrentOperation != null && mCurrentOperation.mClientMonitor == mOwner) { + Slog.d(getTag(), "[Client finished] " + clientMonitor + ", success: " + success); + if (mCurrentOperation != null && mCurrentOperation.isFor(mOwner)) { mCurrentOperation = null; startNextOperationIfIdle(); } else { @@ -83,26 +86,30 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { } @VisibleForTesting - UserAwareBiometricScheduler(@NonNull String tag, @SensorType int sensorType, + public UserAwareBiometricScheduler(@NonNull String tag, + @NonNull Handler handler, + @SensorType int sensorType, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @NonNull IBiometricService biometricService, @NonNull CurrentUserRetriever currentUserRetriever, @NonNull UserSwitchCallback userSwitchCallback, @NonNull CoexCoordinator coexCoordinator) { - super(tag, sensorType, gestureAvailabilityDispatcher, biometricService, + super(tag, handler, sensorType, gestureAvailabilityDispatcher, biometricService, LOG_NUM_RECENT_OPERATIONS, coexCoordinator); mCurrentUserRetriever = currentUserRetriever; mUserSwitchCallback = userSwitchCallback; } - public UserAwareBiometricScheduler(@NonNull String tag, @SensorType int sensorType, + public UserAwareBiometricScheduler(@NonNull String tag, + @SensorType int sensorType, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @NonNull CurrentUserRetriever currentUserRetriever, @NonNull UserSwitchCallback userSwitchCallback) { - this(tag, sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface( - ServiceManager.getService(Context.BIOMETRIC_SERVICE)), currentUserRetriever, - userSwitchCallback, CoexCoordinator.getInstance()); + this(tag, new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher, + IBiometricService.Stub.asInterface( + ServiceManager.getService(Context.BIOMETRIC_SERVICE)), + currentUserRetriever, userSwitchCallback, CoexCoordinator.getInstance()); } @Override @@ -122,7 +129,7 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { } final int currentUserId = mCurrentUserRetriever.getCurrentUserId(); - final int nextUserId = mPendingOperations.getFirst().mClientMonitor.getTargetUserId(); + final int nextUserId = mPendingOperations.getFirst().getTargetUserId(); if (nextUserId == currentUserId) { super.startNextOperationIfIdle(); @@ -133,8 +140,8 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { new ClientFinishedCallback(startClient); Slog.d(getTag(), "[Starting User] " + startClient); - mCurrentOperation = new Operation( - startClient, finishedCallback, Operation.STATE_STARTED); + mCurrentOperation = new BiometricSchedulerOperation( + startClient, finishedCallback, STATE_STARTED); startClient.start(finishedCallback); } else { if (mStopUserClient != null) { @@ -147,8 +154,8 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { Slog.d(getTag(), "[Stopping User] current: " + currentUserId + ", next: " + nextUserId + ". " + mStopUserClient); - mCurrentOperation = new Operation( - mStopUserClient, finishedCallback, Operation.STATE_STARTED); + mCurrentOperation = new BiometricSchedulerOperation( + mStopUserClient, finishedCallback, STATE_STARTED); mStopUserClient.start(finishedCallback); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index 675ee545a14f..039b08e805c1 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java @@ -213,7 +213,7 @@ public class FaceService extends SystemService { } @Override // Binder call - public void enroll(int userId, final IBinder token, final byte[] hardwareAuthToken, + public long enroll(int userId, final IBinder token, final byte[] hardwareAuthToken, final IFaceServiceReceiver receiver, final String opPackageName, final int[] disabledFeatures, Surface previewSurface, boolean debugConsent) { Utils.checkPermission(getContext(), MANAGE_BIOMETRIC); @@ -221,23 +221,24 @@ public class FaceService extends SystemService { final Pair<Integer, ServiceProvider> provider = getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for enroll"); - return; + return -1; } - provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId, + return provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId, receiver, opPackageName, disabledFeatures, previewSurface, debugConsent); } @Override // Binder call - public void enrollRemotely(int userId, final IBinder token, final byte[] hardwareAuthToken, + public long enrollRemotely(int userId, final IBinder token, final byte[] hardwareAuthToken, final IFaceServiceReceiver receiver, final String opPackageName, final int[] disabledFeatures) { Utils.checkPermission(getContext(), MANAGE_BIOMETRIC); // TODO(b/145027036): Implement this. + return -1; } @Override // Binder call - public void cancelEnrollment(final IBinder token) { + public void cancelEnrollment(final IBinder token, long requestId) { Utils.checkPermission(getContext(), MANAGE_BIOMETRIC); final Pair<Integer, ServiceProvider> provider = getSingleProvider(); @@ -246,7 +247,7 @@ public class FaceService extends SystemService { return; } - provider.second.cancelEnrollment(provider.first, token); + provider.second.cancelEnrollment(provider.first, token, requestId); } @Override // Binder call @@ -624,7 +625,7 @@ public class FaceService extends SystemService { private void addHidlProviders(@NonNull List<FaceSensorPropertiesInternal> hidlSensors) { for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) { mServiceProviders.add( - new Face10(getContext(), hidlSensor, mLockoutResetDispatcher)); + Face10.newInstance(getContext(), hidlSensor, mLockoutResetDispatcher)); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java index e099ba372b05..77e431c81192 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java @@ -94,12 +94,12 @@ public interface ServiceProvider { void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token, @NonNull String opPackageName, long challenge); - void scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, + long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, @NonNull int[] disabledFeatures, @Nullable Surface previewSurface, boolean debugConsent); - void cancelEnrollment(int sensorId, @NonNull IBinder token); + void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId); long scheduleFaceDetect(int sensorId, @NonNull IBinder token, int userId, @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java index a806277ed45e..aae4fbe9b0d7 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java @@ -82,13 +82,14 @@ public class FaceEnrollClient extends EnrollClient<ISession> { FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, - @NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, + @NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, long requestId, @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec, @Nullable Surface previewSurface, int sensorId, int maxTemplatesPerUser, boolean debugConsent) { super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils, timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId, false /* shouldVibrate */); + setRequestId(requestId); mEnrollIgnoreList = getContext().getResources() .getIntArray(R.array.config_face_acquire_enroll_ignorelist); mEnrollIgnoreListVendor = getContext().getResources() diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index 4bae7756abe0..ae507abea537 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -327,17 +327,18 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { } @Override - public void scheduleEnroll(int sensorId, @NonNull IBinder token, + public long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, @NonNull int[] disabledFeatures, @Nullable Surface previewSurface, boolean debugConsent) { + final long id = mRequestCounter.incrementAndGet(); mHandler.post(() -> { final int maxTemplatesPerUser = mSensors.get( sensorId).getSensorProperties().maxEnrollmentsPerUser; final FaceEnrollClient client = new FaceEnrollClient(mContext, mSensors.get(sensorId).getLazySession(), token, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, - opPackageName, FaceUtils.getInstance(sensorId), disabledFeatures, + opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures, ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser, debugConsent); scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() { @@ -351,11 +352,13 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { } }); }); + return id; } @Override - public void cancelEnrollment(int sensorId, @NonNull IBinder token) { - mHandler.post(() -> mSensors.get(sensorId).getScheduler().cancelEnrollment(token)); + public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) { + mHandler.post(() -> + mSensors.get(sensorId).getScheduler().cancelEnrollment(token, requestId)); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java index f4dcbbba21d7..e957794372aa 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java @@ -333,12 +333,13 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { Face10(@NonNull Context context, @NonNull FaceSensorPropertiesInternal sensorProps, @NonNull LockoutResetDispatcher lockoutResetDispatcher, + @NonNull Handler handler, @NonNull BiometricScheduler scheduler) { mSensorProperties = sensorProps; mContext = context; mSensorId = sensorProps.sensorId; mScheduler = scheduler; - mHandler = new Handler(Looper.getMainLooper()); + mHandler = handler; mUsageStats = new UsageStats(context); mAuthenticatorIds = new HashMap<>(); mLazyDaemon = Face10.this::getDaemon; @@ -357,9 +358,11 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { } } - public Face10(@NonNull Context context, @NonNull FaceSensorPropertiesInternal sensorProps, + public static Face10 newInstance(@NonNull Context context, + @NonNull FaceSensorPropertiesInternal sensorProps, @NonNull LockoutResetDispatcher lockoutResetDispatcher) { - this(context, sensorProps, lockoutResetDispatcher, + final Handler handler = new Handler(Looper.getMainLooper()); + return new Face10(context, sensorProps, lockoutResetDispatcher, handler, new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityTracker */)); } @@ -573,10 +576,11 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { } @Override - public void scheduleEnroll(int sensorId, @NonNull IBinder token, + public long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, @NonNull int[] disabledFeatures, @Nullable Surface previewSurface, boolean debugConsent) { + final long id = mRequestCounter.incrementAndGet(); mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); @@ -584,7 +588,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, - opPackageName, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures, + opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures, ENROLL_TIMEOUT_SEC, previewSurface, mSensorId); mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() { @@ -598,13 +602,12 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { } }); }); + return id; } @Override - public void cancelEnrollment(int sensorId, @NonNull IBinder token) { - mHandler.post(() -> { - mScheduler.cancelEnrollment(token); - }); + public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) { + mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId)); } @Override @@ -893,6 +896,8 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { boolean success) { if (success) { mCurrentUserId = targetUserId; + } else { + Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId); } } }); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java index 80828cced4e8..31e5c86103fb 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java @@ -53,12 +53,13 @@ public class FaceEnrollClient extends EnrollClient<IBiometricsFace> { FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, - @NonNull byte[] hardwareAuthToken, @NonNull String owner, + @NonNull byte[] hardwareAuthToken, @NonNull String owner, long requestId, @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec, @Nullable Surface previewSurface, int sensorId) { super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId, false /* shouldVibrate */); + setRequestId(requestId); mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length); mEnrollIgnoreList = getContext().getResources() .getIntArray(R.array.config_face_acquire_enroll_ignorelist); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index c5d33ed7400b..b44f4dc68274 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -249,7 +249,7 @@ public class FingerprintService extends SystemService { } @Override // Binder call - public void enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken, + public long enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken, final int userId, final IFingerprintServiceReceiver receiver, final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) { Utils.checkPermission(getContext(), MANAGE_FINGERPRINT); @@ -257,15 +257,15 @@ public class FingerprintService extends SystemService { final Pair<Integer, ServiceProvider> provider = getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for enroll"); - return; + return -1; } - provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId, + return provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId, receiver, opPackageName, enrollReason); } @Override // Binder call - public void cancelEnrollment(final IBinder token) { + public void cancelEnrollment(final IBinder token, long requestId) { Utils.checkPermission(getContext(), MANAGE_FINGERPRINT); final Pair<Integer, ServiceProvider> provider = getSingleProvider(); @@ -274,7 +274,7 @@ public class FingerprintService extends SystemService { return; } - provider.second.cancelEnrollment(provider.first, token); + provider.second.cancelEnrollment(provider.first, token, requestId); } @SuppressWarnings("deprecation") @@ -818,7 +818,7 @@ public class FingerprintService extends SystemService { mLockoutResetDispatcher, mGestureAvailabilityDispatcher); } else { fingerprint21 = Fingerprint21.newInstance(getContext(), - mFingerprintStateCallback, hidlSensor, + mFingerprintStateCallback, hidlSensor, mHandler, mLockoutResetDispatcher, mGestureAvailabilityDispatcher); } mServiceProviders.add(fingerprint21); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java index 1772f814dd10..535705c63cab 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java @@ -88,11 +88,11 @@ public interface ServiceProvider { /** * Schedules fingerprint enrollment. */ - void scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, + long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason); - void cancelEnrollment(int sensorId, @NonNull IBinder token); + void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId); long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId, @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index ccb34aad3198..67507ccbbbfe 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -57,7 +57,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { private boolean mIsPointerDown; FingerprintEnrollClient(@NonNull Context context, - @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, + @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId, @@ -69,6 +69,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, 0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId, !sensorProps.isAnyUdfpsType() /* shouldVibrate */); + setRequestId(requestId); mSensorProps = sensorProps; mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); mMaxTemplatesPerUser = maxTemplatesPerUser; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 0defc3fb6a50..e8fde1d38aa2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -343,15 +343,16 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } @Override - public void scheduleEnroll(int sensorId, @NonNull IBinder token, + public long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason) { + final long id = mRequestCounter.incrementAndGet(); mHandler.post(() -> { final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties() .maxEnrollmentsPerUser; final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext, - mSensors.get(sensorId).getLazySession(), token, + mSensors.get(sensorId).getLazySession(), token, id, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, opPackageName, FingerprintUtils.getInstance(sensorId), sensorId, mSensors.get(sensorId).getSensorProperties(), @@ -374,11 +375,13 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } }); }); + return id; } @Override - public void cancelEnrollment(int sensorId, @NonNull IBinder token) { - mHandler.post(() -> mSensors.get(sensorId).getScheduler().cancelEnrollment(token)); + public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) { + mHandler.post(() -> + mSensors.get(sensorId).getScheduler().cancelEnrollment(token, requestId)); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index 5f2f4cf6ef3c..6feb5fa418bb 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -42,7 +42,6 @@ import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.Handler; import android.os.IBinder; import android.os.IHwBinder; -import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; @@ -320,7 +319,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider Fingerprint21(@NonNull Context context, @NonNull FingerprintStateCallback fingerprintStateCallback, @NonNull FingerprintSensorPropertiesInternal sensorProps, - @NonNull BiometricScheduler scheduler, @NonNull Handler handler, + @NonNull BiometricScheduler scheduler, + @NonNull Handler handler, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull HalResultController controller) { mContext = context; @@ -356,16 +356,15 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider public static Fingerprint21 newInstance(@NonNull Context context, @NonNull FingerprintStateCallback fingerprintStateCallback, @NonNull FingerprintSensorPropertiesInternal sensorProps, + @NonNull Handler handler, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { - final Handler handler = new Handler(Looper.getMainLooper()); final BiometricScheduler scheduler = new BiometricScheduler(TAG, BiometricScheduler.sensorTypeFromFingerprintProperties(sensorProps), gestureAvailabilityDispatcher); final HalResultController controller = new HalResultController(sensorProps.sensorId, - context, handler, - scheduler); + context, handler, scheduler); return new Fingerprint21(context, fingerprintStateCallback, sensorProps, scheduler, handler, lockoutResetDispatcher, controller); } @@ -491,19 +490,25 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider !getEnrolledFingerprints(mSensorProperties.sensorId, targetUserId).isEmpty(); final FingerprintUpdateActiveUserClient client = new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId, - mContext.getOpPackageName(), mSensorProperties.sensorId, mCurrentUserId, - hasEnrolled, mAuthenticatorIds, force); + mContext.getOpPackageName(), mSensorProperties.sensorId, + this::getCurrentUser, hasEnrolled, mAuthenticatorIds, force); mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() { @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { if (success) { mCurrentUserId = targetUserId; + } else { + Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId); } } }); } + private int getCurrentUser() { + return mCurrentUserId; + } + @Override public boolean containsSensor(int sensorId) { return mSensorProperties.sensorId == sensorId; @@ -558,18 +563,20 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider } @Override - public void scheduleEnroll(int sensorId, @NonNull IBinder token, + public long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason) { + final long id = mRequestCounter.incrementAndGet(); mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext, - mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, - hardwareAuthToken, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), - ENROLL_TIMEOUT_SEC, mSensorProperties.sensorId, mUdfpsOverlayController, - mSidefpsController, enrollReason); + mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver), + userId, hardwareAuthToken, opPackageName, + FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC, + mSensorProperties.sensorId, mUdfpsOverlayController, mSidefpsController, + enrollReason); mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { @@ -588,13 +595,12 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider } }); }); + return id; } @Override - public void cancelEnrollment(int sensorId, @NonNull IBinder token) { - mHandler.post(() -> { - mScheduler.cancelEnrollment(token); - }); + public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) { + mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId)); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java index dd68b4d37e2a..273f8a545db5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java @@ -26,7 +26,6 @@ import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; -import android.hardware.fingerprint.FingerprintStateListener; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.Handler; import android.os.IBinder; @@ -135,43 +134,16 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage @NonNull private final RestartAuthRunnable mRestartAuthRunnable; private static class TestableBiometricScheduler extends BiometricScheduler { - @NonNull private final TestableInternalCallback mInternalCallback; @NonNull private Fingerprint21UdfpsMock mFingerprint21; - TestableBiometricScheduler(@NonNull String tag, + TestableBiometricScheduler(@NonNull String tag, @NonNull Handler handler, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { - super(tag, BiometricScheduler.SENSOR_TYPE_FP_OTHER, - gestureAvailabilityDispatcher); - mInternalCallback = new TestableInternalCallback(); - } - - class TestableInternalCallback extends InternalCallback { - @Override - public void onClientStarted(BaseClientMonitor clientMonitor) { - super.onClientStarted(clientMonitor); - Slog.d(TAG, "Client started: " + clientMonitor); - mFingerprint21.setDebugMessage("Started: " + clientMonitor); - } - - @Override - public void onClientFinished(BaseClientMonitor clientMonitor, boolean success) { - super.onClientFinished(clientMonitor, success); - Slog.d(TAG, "Client finished: " + clientMonitor); - mFingerprint21.setDebugMessage("Finished: " + clientMonitor); - } + super(tag, BiometricScheduler.SENSOR_TYPE_FP_OTHER, gestureAvailabilityDispatcher); } void init(@NonNull Fingerprint21UdfpsMock fingerprint21) { mFingerprint21 = fingerprint21; } - - /** - * Expose the internal finish callback so it can be used for testing - */ - @Override - @NonNull protected InternalCallback getInternalCallback() { - return mInternalCallback; - } } /** @@ -280,7 +252,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage final Handler handler = new Handler(Looper.getMainLooper()); final TestableBiometricScheduler scheduler = - new TestableBiometricScheduler(TAG, gestureAvailabilityDispatcher); + new TestableBiometricScheduler(TAG, handler, gestureAvailabilityDispatcher); final MockHalResultController controller = new MockHalResultController(sensorProps.sensorId, context, handler, scheduler); return new Fingerprint21UdfpsMock(context, fingerprintStateCallback, sensorProps, scheduler, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java index 1ebf44ca707f..cc50bdfb59ae 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java @@ -55,7 +55,7 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint FingerprintEnrollClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token, - @NonNull ClientMonitorCallbackConverter listener, int userId, + long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId, @Nullable IUdfpsOverlayController udfpsOverlayController, @@ -64,6 +64,7 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId, true /* shouldVibrate */); + setRequestId(requestId); mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); mEnrollReason = enrollReason; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java index fd38bdd1201e..a2c18923c00e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java @@ -31,6 +31,7 @@ import com.android.server.biometrics.sensors.HalClientMonitor; import java.io.File; import java.util.Map; +import java.util.function.Supplier; /** * Sets the HAL's current active user, and updates the framework's authenticatorId cache. @@ -40,7 +41,7 @@ public class FingerprintUpdateActiveUserClient extends HalClientMonitor<IBiometr private static final String TAG = "FingerprintUpdateActiveUserClient"; private static final String FP_DATA_DIR = "fpdata"; - private final int mCurrentUserId; + private final Supplier<Integer> mCurrentUserId; private final boolean mForceUpdateAuthenticatorId; private final boolean mHasEnrolledBiometrics; private final Map<Integer, Long> mAuthenticatorIds; @@ -48,8 +49,9 @@ public class FingerprintUpdateActiveUserClient extends HalClientMonitor<IBiometr FingerprintUpdateActiveUserClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, int userId, - @NonNull String owner, int sensorId, int currentUserId, boolean hasEnrolledBiometrics, - @NonNull Map<Integer, Long> authenticatorIds, boolean forceUpdateAuthenticatorId) { + @NonNull String owner, int sensorId, Supplier<Integer> currentUserId, + boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds, + boolean forceUpdateAuthenticatorId) { super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner, 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN); @@ -63,7 +65,7 @@ public class FingerprintUpdateActiveUserClient extends HalClientMonitor<IBiometr public void start(@NonNull Callback callback) { super.start(callback); - if (mCurrentUserId == getTargetUserId() && !mForceUpdateAuthenticatorId) { + if (mCurrentUserId.get() == getTargetUserId() && !mForceUpdateAuthenticatorId) { Slog.d(TAG, "Already user: " + mCurrentUserId + ", returning"); callback.onClientFinished(this, true /* success */); return; @@ -109,8 +111,10 @@ public class FingerprintUpdateActiveUserClient extends HalClientMonitor<IBiometr @Override protected void startHalOperation() { try { - getFreshDaemon().setActiveGroup(getTargetUserId(), mDirectory.getAbsolutePath()); - mAuthenticatorIds.put(getTargetUserId(), mHasEnrolledBiometrics + final int targetId = getTargetUserId(); + Slog.d(TAG, "Setting active user: " + targetId); + getFreshDaemon().setActiveGroup(targetId, mDirectory.getAbsolutePath()); + mAuthenticatorIds.put(targetId, mHasEnrolledBiometrics ? getFreshDaemon().getAuthenticatorId() : 0L); mCallback.onClientFinished(this, true /* success */); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java index 4d9253eff69e..718f98a0f04b 100644 --- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java +++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java @@ -215,7 +215,9 @@ class GnssNetworkConnectivityHandler { } @Override public void onPreciseCallStateChanged(PreciseCallState state) { - if (state.PRECISE_CALL_STATE_ACTIVE == state.getForegroundCallState()) { + if (PreciseCallState.PRECISE_CALL_STATE_ACTIVE == state.getForegroundCallState() + || PreciseCallState.PRECISE_CALL_STATE_DIALING + == state.getForegroundCallState()) { mActiveSubId = mSubId; if (DEBUG) Log.d(TAG, "mActiveSubId: " + mActiveSubId); } diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING index 9182d811d56d..b2088fdb17d1 100644 --- a/services/core/java/com/android/server/pm/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/TEST_MAPPING @@ -41,20 +41,6 @@ ] }, { - "name": "CtsContentTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.content.pm.cts" - } - ] - }, - { "name": "GtsContentTestCases", "options": [ { @@ -107,6 +93,22 @@ ] } ], + "presubmit-large": [ + { + "name": "CtsContentTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "include-filter": "android.content.pm.cts" + } + ] + } + ], "postsubmit": [ { "name": "CtsPermissionTestCases", diff --git a/services/incremental/TEST_MAPPING b/services/incremental/TEST_MAPPING index 6aa8a939739a..f2ad068854c4 100644 --- a/services/incremental/TEST_MAPPING +++ b/services/incremental/TEST_MAPPING @@ -1,20 +1,6 @@ { "presubmit": [ { - "name": "CtsContentTestCases", - "options": [ - { - "include-filter": "android.content.pm.cts.PackageManagerShellCommandTest" - }, - { - "include-filter": "android.content.pm.cts.PackageManagerShellCommandIncrementalTest" - }, - { - "include-filter": "android.content.pm.cts.ChecksumsTest" - } - ] - }, - { "name": "CtsPackageManagerStatsHostTestCases", "options": [ { @@ -29,6 +15,20 @@ "presubmit-large": [ { "name": "CtsInstalledLoadingProgressHostTests" + }, + { + "name": "CtsContentTestCases", + "options": [ + { + "include-filter": "android.content.pm.cts.PackageManagerShellCommandTest" + }, + { + "include-filter": "android.content.pm.cts.PackageManagerShellCommandIncrementalTest" + }, + { + "include-filter": "android.content.pm.cts.ChecksumsTest" + } + ] } ] } diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java index 2eb9e34b3fd0..a0d86c9268fb 100644 --- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java @@ -187,6 +187,30 @@ public class GestureLauncherServiceTest { } @Test + public void testGetEmergencyGesturePowerButtonCooldownPeriodMs_enabled() { + withEmergencyGesturePowerButtonCooldownPeriodMsValue(4000); + assertEquals(4000, + mGestureLauncherService.getEmergencyGesturePowerButtonCooldownPeriodMs(mContext, + FAKE_USER_ID)); + } + + @Test + public void testGetEmergencyGesturePowerButtonCooldownPeriodMs_disabled() { + withEmergencyGesturePowerButtonCooldownPeriodMsValue(0); + assertEquals(0, + mGestureLauncherService.getEmergencyGesturePowerButtonCooldownPeriodMs(mContext, + FAKE_USER_ID)); + } + + @Test + public void testGetEmergencyGesturePowerButtonCooldownPeriodMs_cappedAtMaximum() { + withEmergencyGesturePowerButtonCooldownPeriodMsValue(10000); + assertEquals(GestureLauncherService.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX, + mGestureLauncherService.getEmergencyGesturePowerButtonCooldownPeriodMs(mContext, + FAKE_USER_ID)); + } + + @Test public void testHandleCameraLaunchGesture_userSetupComplete() { withUserSetupCompleteValue(true); @@ -645,6 +669,211 @@ public class GestureLauncherServiceTest { } @Test + public void testInterceptPowerKeyDown_triggerEmergency_singleTaps_cooldownTriggered() { + // Enable power button cooldown + withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000); + mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs(); + + // Trigger emergency by tapping button 5 times + long eventTime = triggerEmergencyGesture(); + + // Add enough interval to reset consecutive tap count + long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; + eventTime += interval; + + // Subsequent single tap is intercepted, but should not trigger any gesture + KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, + IGNORED_REPEAT); + boolean interactive = true; + MutableBoolean outLaunched = new MutableBoolean(true); + boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, + outLaunched); + assertTrue(intercepted); + assertFalse(outLaunched.value); + + // Add enough interval to reset consecutive tap count + interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; + eventTime += interval; + + // Another single tap should be the same (intercepted but should not trigger gesture) + keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, + IGNORED_REPEAT); + interactive = true; + outLaunched = new MutableBoolean(true); + intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, + outLaunched); + assertTrue(intercepted); + assertFalse(outLaunched.value); + } + + @Test + public void + testInterceptPowerKeyDown_triggerEmergency_cameraGestureEnabled_doubleTap_cooldownTriggered() { + // Enable camera double tap gesture + withCameraDoubleTapPowerEnableConfigValue(true); + withCameraDoubleTapPowerDisableSettingValue(0); + mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); + + // Enable power button cooldown + withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000); + mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs(); + + // Trigger emergency by tapping button 5 times + long eventTime = triggerEmergencyGesture(); + + // Add enough interval to reset consecutive tap count + long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; + eventTime += interval; + + // Subsequent double tap is intercepted, but should not trigger any gesture + for (int i = 0; i < 2; i++) { + KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, + IGNORED_CODE, IGNORED_REPEAT); + boolean interactive = true; + MutableBoolean outLaunched = new MutableBoolean(true); + boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, + interactive, outLaunched); + assertTrue(intercepted); + assertFalse(outLaunched.value); + interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1; + eventTime += interval; + } + } + + @Test + public void testInterceptPowerKeyDown_triggerEmergency_fiveTaps_cooldownTriggered() { + // Enable power button cooldown + withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000); + mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs(); + + // Trigger emergency by tapping button 5 times + long eventTime = triggerEmergencyGesture(); + + // Add enough interval to reset consecutive tap count + long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; + eventTime += interval; + + // Subsequent 5 taps are intercepted, but should not trigger any gesture + for (int i = 0; i < 5; i++) { + KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, + IGNORED_CODE, IGNORED_REPEAT); + boolean interactive = true; + MutableBoolean outLaunched = new MutableBoolean(true); + boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, + interactive, outLaunched); + assertTrue(intercepted); + assertFalse(outLaunched.value); + interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1; + eventTime += interval; + } + } + + @Test + public void testInterceptPowerKeyDown_triggerEmergency_longPress_cooldownTriggered() { + // Enable power button cooldown + withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000); + mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs(); + + // Trigger emergency by tapping button 5 times + long eventTime = triggerEmergencyGesture(); + + // Add enough interval to reset consecutive tap count + long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; + eventTime += interval; + + // Subsequent long press is intercepted, but should not trigger any gesture + KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, + IGNORED_REPEAT, IGNORED_META_STATE, IGNORED_DEVICE_ID, IGNORED_SCANCODE, + KeyEvent.FLAG_LONG_PRESS); + + boolean interactive = true; + MutableBoolean outLaunched = new MutableBoolean(true); + boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, + outLaunched); + assertTrue(intercepted); + assertFalse(outLaunched.value); + } + + @Test + public void testInterceptPowerKeyDown_triggerEmergency_cooldownDisabled_cooldownNotTriggered() { + // Disable power button cooldown by setting cooldown period to 0 + withEmergencyGesturePowerButtonCooldownPeriodMsValue(0); + mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs(); + + // Trigger emergency by tapping button 5 times + long eventTime = triggerEmergencyGesture(); + + // Add enough interval to reset consecutive tap count + long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; + eventTime += interval; + + // Subsequent single tap is NOT intercepted + KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, + IGNORED_REPEAT); + boolean interactive = true; + MutableBoolean outLaunched = new MutableBoolean(true); + boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, + outLaunched); + assertFalse(intercepted); + assertFalse(outLaunched.value); + + // Add enough interval to reset consecutive tap count + interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; + eventTime += interval; + + // Long press also NOT intercepted + keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, + IGNORED_REPEAT, IGNORED_META_STATE, IGNORED_DEVICE_ID, IGNORED_SCANCODE, + KeyEvent.FLAG_LONG_PRESS); + interactive = true; + outLaunched = new MutableBoolean(true); + intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, + outLaunched); + assertFalse(intercepted); + assertFalse(outLaunched.value); + } + + @Test + public void + testInterceptPowerKeyDown_triggerEmergency_outsideCooldownPeriod_cooldownNotTriggered() { + // Enable power button cooldown + withEmergencyGesturePowerButtonCooldownPeriodMsValue(5000); + mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs(); + + // Trigger emergency by tapping button 5 times + long eventTime = triggerEmergencyGesture(); + + // Add enough interval to be outside of cooldown period + long interval = 5001; + eventTime += interval; + + // Subsequent single tap is NOT intercepted + KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, + IGNORED_REPEAT); + boolean interactive = true; + MutableBoolean outLaunched = new MutableBoolean(true); + boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, + outLaunched); + assertFalse(intercepted); + assertFalse(outLaunched.value); + + // Add enough interval to reset consecutive tap count + interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1; + eventTime += interval; + + // Long press also NOT intercepted + keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, + IGNORED_REPEAT, IGNORED_META_STATE, IGNORED_DEVICE_ID, IGNORED_SCANCODE, + KeyEvent.FLAG_LONG_PRESS); + interactive = true; + outLaunched = new MutableBoolean(true); + intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, + outLaunched); + assertFalse(intercepted); + assertFalse(outLaunched.value); + } + + @Test public void testInterceptPowerKeyDown_longpress() { withCameraDoubleTapPowerEnableConfigValue(true); withCameraDoubleTapPowerDisableSettingValue(0); @@ -1153,6 +1382,45 @@ public class GestureLauncherServiceTest { assertEquals(1, tapCounts.get(1).intValue()); } + /** + * Helper method to trigger emergency gesture by pressing button for 5 times. + * @return last event time. + */ + private long triggerEmergencyGesture() { + // Enable emergency power gesture + withEmergencyGestureEnabledConfigValue(true); + withEmergencyGestureEnabledSettingValue(true); + mGestureLauncherService.updateEmergencyGestureEnabled(); + withUserSetupCompleteValue(true); + + // 4 button presses + long eventTime = INITIAL_EVENT_TIME_MILLIS; + boolean interactive = true; + KeyEvent keyEvent; + MutableBoolean outLaunched = new MutableBoolean(false); + for (int i = 0; i < 4; i++) { + keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, + IGNORED_REPEAT); + mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched); + final long interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1; + eventTime += interval; + } + + // 5th button press should trigger the emergency flow + keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, + IGNORED_REPEAT); + outLaunched.value = false; + boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, + outLaunched); + assertTrue(outLaunched.value); + assertTrue(intercepted); + verify(mUiEventLogger, times(1)) + .log(GestureLauncherService.GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER); + verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected(); + + return eventTime; + } + private void withCameraDoubleTapPowerEnableConfigValue(boolean enableConfigValue) { when(mResources.getBoolean( com.android.internal.R.bool.config_cameraDoubleTapPowerGestureEnabled)) @@ -1181,6 +1449,14 @@ public class GestureLauncherServiceTest { UserHandle.USER_CURRENT); } + private void withEmergencyGesturePowerButtonCooldownPeriodMsValue(int period) { + Settings.Secure.putIntForUser( + mContentResolver, + Settings.Secure.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS, + period, + UserHandle.USER_CURRENT); + } + private void withUserSetupCompleteValue(boolean userSetupComplete) { int userSetupCompleteValue = userSetupComplete ? 1 : 0; Settings.Secure.putIntForUser( diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java new file mode 100644 index 000000000000..d4bac2c0402d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java @@ -0,0 +1,326 @@ +/* + * 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.server.biometrics.sensors; + +import static android.testing.TestableLooper.RunWithLooper; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.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 static org.testng.Assert.assertThrows; + +import android.os.Handler; +import android.platform.test.annotations.Presubmit; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@Presubmit +@RunWith(AndroidTestingRunner.class) +@RunWithLooper(setAsMainLooper = true) +@SmallTest +public class BiometricSchedulerOperationTest { + + public interface FakeHal {} + public abstract static class InterruptableMonitor<T> + extends HalClientMonitor<T> implements Interruptable { + public InterruptableMonitor() { + super(null, null, null, null, 0, null, 0, 0, 0, 0, 0); + } + } + + @Mock + private InterruptableMonitor<FakeHal> mClientMonitor; + @Mock + private BaseClientMonitor.Callback mClientCallback; + @Mock + private FakeHal mHal; + @Captor + ArgumentCaptor<BaseClientMonitor.Callback> mStartCallback; + + private Handler mHandler; + private BiometricSchedulerOperation mOperation; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mHandler = new Handler(TestableLooper.get(this).getLooper()); + mOperation = new BiometricSchedulerOperation(mClientMonitor, mClientCallback); + } + + @Test + public void testStartWithCookie() { + final int cookie = 200; + when(mClientMonitor.getCookie()).thenReturn(cookie); + when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); + + assertThat(mOperation.isReadyToStart()).isEqualTo(cookie); + assertThat(mOperation.isStarted()).isFalse(); + assertThat(mOperation.isCanceling()).isFalse(); + assertThat(mOperation.isFinished()).isFalse(); + + final boolean started = mOperation.startWithCookie( + mock(BaseClientMonitor.Callback.class), cookie); + + assertThat(started).isTrue(); + verify(mClientMonitor).start(mStartCallback.capture()); + mStartCallback.getValue().onClientStarted(mClientMonitor); + assertThat(mOperation.isStarted()).isTrue(); + } + + @Test + public void testNoStartWithoutCookie() { + final int goodCookie = 20; + final int badCookie = 22; + when(mClientMonitor.getCookie()).thenReturn(goodCookie); + when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); + + assertThat(mOperation.isReadyToStart()).isEqualTo(goodCookie); + final boolean started = mOperation.startWithCookie( + mock(BaseClientMonitor.Callback.class), badCookie); + + assertThat(started).isFalse(); + assertThat(mOperation.isStarted()).isFalse(); + assertThat(mOperation.isCanceling()).isFalse(); + assertThat(mOperation.isFinished()).isFalse(); + } + + @Test + public void startsWhenReadyAndHalAvailable() { + when(mClientMonitor.getCookie()).thenReturn(0); + when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); + + final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class); + mOperation.start(cb); + verify(mClientMonitor).start(mStartCallback.capture()); + mStartCallback.getValue().onClientStarted(mClientMonitor); + + assertThat(mOperation.isStarted()).isTrue(); + assertThat(mOperation.isCanceling()).isFalse(); + assertThat(mOperation.isFinished()).isFalse(); + + verify(mClientCallback).onClientStarted(eq(mClientMonitor)); + verify(cb).onClientStarted(eq(mClientMonitor)); + verify(mClientCallback, never()).onClientFinished(any(), anyBoolean()); + verify(cb, never()).onClientFinished(any(), anyBoolean()); + + mStartCallback.getValue().onClientFinished(mClientMonitor, true); + + assertThat(mOperation.isFinished()).isTrue(); + assertThat(mOperation.isCanceling()).isFalse(); + verify(mClientMonitor).destroy(); + verify(cb).onClientFinished(eq(mClientMonitor), eq(true)); + } + + @Test + public void startFailsWhenReadyButHalNotAvailable() { + when(mClientMonitor.getCookie()).thenReturn(0); + when(mClientMonitor.getFreshDaemon()).thenReturn(null); + + final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class); + mOperation.start(cb); + verify(mClientMonitor, never()).start(any()); + + assertThat(mOperation.isStarted()).isFalse(); + assertThat(mOperation.isCanceling()).isFalse(); + assertThat(mOperation.isFinished()).isTrue(); + + verify(mClientCallback, never()).onClientStarted(eq(mClientMonitor)); + verify(cb, never()).onClientStarted(eq(mClientMonitor)); + verify(mClientCallback).onClientFinished(eq(mClientMonitor), eq(false)); + verify(cb).onClientFinished(eq(mClientMonitor), eq(false)); + } + + @Test + public void doesNotStartWithCookie() { + when(mClientMonitor.getCookie()).thenReturn(9); + assertThrows(IllegalStateException.class, + () -> mOperation.start(mock(BaseClientMonitor.Callback.class))); + } + + @Test + public void cannotRestart() { + when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); + + mOperation.start(mock(BaseClientMonitor.Callback.class)); + + assertThrows(IllegalStateException.class, + () -> mOperation.start(mock(BaseClientMonitor.Callback.class))); + } + + @Test + public void abortsNotRunning() { + when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); + + mOperation.abort(); + + assertThat(mOperation.isFinished()).isTrue(); + verify(mClientMonitor).unableToStart(); + verify(mClientMonitor).destroy(); + assertThrows(IllegalStateException.class, + () -> mOperation.start(mock(BaseClientMonitor.Callback.class))); + } + + @Test + public void cannotAbortRunning() { + when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); + + mOperation.start(mock(BaseClientMonitor.Callback.class)); + + assertThrows(IllegalStateException.class, () -> mOperation.abort()); + } + + @Test + public void cancel() { + when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); + + final BaseClientMonitor.Callback startCb = mock(BaseClientMonitor.Callback.class); + final BaseClientMonitor.Callback cancelCb = mock(BaseClientMonitor.Callback.class); + mOperation.start(startCb); + verify(mClientMonitor).start(mStartCallback.capture()); + mStartCallback.getValue().onClientStarted(mClientMonitor); + mOperation.cancel(mHandler, cancelCb); + + assertThat(mOperation.isCanceling()).isTrue(); + verify(mClientMonitor).cancel(); + verify(mClientMonitor, never()).cancelWithoutStarting(any()); + verify(mClientMonitor, never()).destroy(); + + mStartCallback.getValue().onClientFinished(mClientMonitor, true); + + assertThat(mOperation.isFinished()).isTrue(); + assertThat(mOperation.isCanceling()).isFalse(); + verify(mClientMonitor).destroy(); + + // should be unused since the operation was started + verify(cancelCb, never()).onClientStarted(any()); + verify(cancelCb, never()).onClientFinished(any(), anyBoolean()); + } + + @Test + public void cancelWithoutStarting() { + when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); + + final BaseClientMonitor.Callback cancelCb = mock(BaseClientMonitor.Callback.class); + mOperation.cancel(mHandler, cancelCb); + + assertThat(mOperation.isCanceling()).isTrue(); + ArgumentCaptor<BaseClientMonitor.Callback> cbCaptor = + ArgumentCaptor.forClass(BaseClientMonitor.Callback.class); + verify(mClientMonitor).cancelWithoutStarting(cbCaptor.capture()); + + cbCaptor.getValue().onClientFinished(mClientMonitor, true); + verify(cancelCb).onClientFinished(eq(mClientMonitor), eq(true)); + verify(mClientMonitor, never()).start(any()); + verify(mClientMonitor, never()).cancel(); + verify(mClientMonitor).destroy(); + } + + @Test + public void markCanceling() { + when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); + + mOperation.markCanceling(); + + assertThat(mOperation.isMarkedCanceling()).isTrue(); + assertThat(mOperation.isCanceling()).isFalse(); + assertThat(mOperation.isFinished()).isFalse(); + verify(mClientMonitor, never()).start(any()); + verify(mClientMonitor, never()).cancel(); + verify(mClientMonitor, never()).cancelWithoutStarting(any()); + verify(mClientMonitor, never()).unableToStart(); + verify(mClientMonitor, never()).destroy(); + } + + @Test + public void cancelPendingWithCookie() { + markCancellingAndStart(2); + } + + @Test + public void cancelPendingWithoutCookie() { + markCancellingAndStart(null); + } + + private void markCancellingAndStart(Integer withCookie) { + when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); + if (withCookie != null) { + when(mClientMonitor.getCookie()).thenReturn(withCookie); + } + + mOperation.markCanceling(); + final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class); + if (withCookie != null) { + mOperation.startWithCookie(cb, withCookie); + } else { + mOperation.start(cb); + } + + assertThat(mOperation.isFinished()).isTrue(); + verify(cb).onClientFinished(eq(mClientMonitor), eq(true)); + verify(mClientMonitor, never()).start(any()); + verify(mClientMonitor, never()).cancel(); + verify(mClientMonitor, never()).cancelWithoutStarting(any()); + verify(mClientMonitor, never()).unableToStart(); + verify(mClientMonitor).destroy(); + } + + @Test + public void cancelWatchdogWhenStarted() { + cancelWatchdog(true); + } + + @Test + public void cancelWatchdogWithoutStarting() { + cancelWatchdog(false); + } + + private void cancelWatchdog(boolean start) { + when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); + + mOperation.start(mock(BaseClientMonitor.Callback.class)); + if (start) { + verify(mClientMonitor).start(mStartCallback.capture()); + mStartCallback.getValue().onClientStarted(mClientMonitor); + } + mOperation.cancel(mHandler, mock(BaseClientMonitor.Callback.class)); + + assertThat(mOperation.isCanceling()).isTrue(); + + // omit call to onClientFinished and trigger watchdog + mOperation.mCancelWatchdog.run(); + + assertThat(mOperation.isFinished()).isTrue(); + assertThat(mOperation.isCanceling()).isFalse(); + verify(mClientMonitor).destroy(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java index d192697827f6..ac0831983262 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java @@ -16,10 +16,14 @@ package com.android.server.biometrics.sensors; +import static android.testing.TestableLooper.RunWithLooper; + import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -34,10 +38,13 @@ import android.content.Context; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.IBiometricService; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.testing.AndroidTestingRunner; import android.testing.TestableContext; +import android.testing.TestableLooper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -46,16 +53,18 @@ import androidx.test.filters.SmallTest; import com.android.server.biometrics.nano.BiometricSchedulerProto; import com.android.server.biometrics.nano.BiometricsProto; -import com.android.server.biometrics.sensors.BiometricScheduler.Operation; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @Presubmit @SmallTest +@RunWith(AndroidTestingRunner.class) +@RunWithLooper(setAsMainLooper = true) public class BiometricSchedulerTest { private static final String TAG = "BiometricSchedulerTest"; @@ -76,8 +85,9 @@ public class BiometricSchedulerTest { public void setUp() { MockitoAnnotations.initMocks(this); mToken = new Binder(); - mScheduler = new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_UNKNOWN, - null /* gestureAvailabilityTracker */, mBiometricService, LOG_NUM_RECENT_OPERATIONS, + mScheduler = new BiometricScheduler(TAG, new Handler(TestableLooper.get(this).getLooper()), + BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityTracker */, + mBiometricService, LOG_NUM_RECENT_OPERATIONS, CoexCoordinator.getInstance()); } @@ -86,9 +96,9 @@ public class BiometricSchedulerTest { final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class); final HalClientMonitor<Object> client1 = - new TestClientMonitor(mContext, mToken, nonNullDaemon); + new TestHalClientMonitor(mContext, mToken, nonNullDaemon); final HalClientMonitor<Object> client2 = - new TestClientMonitor(mContext, mToken, nonNullDaemon); + new TestHalClientMonitor(mContext, mToken, nonNullDaemon); mScheduler.scheduleClientMonitor(client1); mScheduler.scheduleClientMonitor(client2); @@ -99,20 +109,17 @@ public class BiometricSchedulerTest { @Test public void testRemovesPendingOperations_whenNullHal_andNotBiometricPrompt() { // Even if second client has a non-null daemon, it needs to be canceled. - Object daemon2 = mock(Object.class); - - final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> null; - final HalClientMonitor.LazyDaemon<Object> lazyDaemon2 = () -> daemon2; - - final TestClientMonitor client1 = new TestClientMonitor(mContext, mToken, lazyDaemon1); - final TestClientMonitor client2 = new TestClientMonitor(mContext, mToken, lazyDaemon2); + final TestHalClientMonitor client1 = new TestHalClientMonitor( + mContext, mToken, () -> null); + final TestHalClientMonitor client2 = new TestHalClientMonitor( + mContext, mToken, () -> mock(Object.class)); final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class); final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class); // Pretend the scheduler is busy so the first operation doesn't start right away. We want // to pretend like there are two operations in the queue before kicking things off - mScheduler.mCurrentOperation = new BiometricScheduler.Operation( + mScheduler.mCurrentOperation = new BiometricSchedulerOperation( mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class)); mScheduler.scheduleClientMonitor(client1, callback1); @@ -122,11 +129,11 @@ public class BiometricSchedulerTest { mScheduler.scheduleClientMonitor(client2, callback2); waitForIdle(); - assertTrue(client1.wasUnableToStart()); + assertTrue(client1.mUnableToStart); verify(callback1).onClientFinished(eq(client1), eq(false) /* success */); verify(callback1, never()).onClientStarted(any()); - assertTrue(client2.wasUnableToStart()); + assertTrue(client2.mUnableToStart); verify(callback2).onClientFinished(eq(client2), eq(false) /* success */); verify(callback2, never()).onClientStarted(any()); @@ -138,21 +145,19 @@ public class BiometricSchedulerTest { // Second non-BiometricPrompt client has a valid daemon final Object daemon2 = mock(Object.class); - final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> null; - final HalClientMonitor.LazyDaemon<Object> lazyDaemon2 = () -> daemon2; - final ClientMonitorCallbackConverter listener1 = mock(ClientMonitorCallbackConverter.class); final TestAuthenticationClient client1 = - new TestAuthenticationClient(mContext, lazyDaemon1, mToken, listener1); - final TestClientMonitor client2 = new TestClientMonitor(mContext, mToken, lazyDaemon2); + new TestAuthenticationClient(mContext, () -> null, mToken, listener1); + final TestHalClientMonitor client2 = + new TestHalClientMonitor(mContext, mToken, () -> daemon2); final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class); final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class); // Pretend the scheduler is busy so the first operation doesn't start right away. We want // to pretend like there are two operations in the queue before kicking things off - mScheduler.mCurrentOperation = new BiometricScheduler.Operation( + mScheduler.mCurrentOperation = new BiometricSchedulerOperation( mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class)); mScheduler.scheduleClientMonitor(client1, callback1); @@ -172,8 +177,8 @@ public class BiometricSchedulerTest { verify(callback1, never()).onClientStarted(any()); // Client 2 was able to start - assertFalse(client2.wasUnableToStart()); - assertTrue(client2.hasStarted()); + assertFalse(client2.mUnableToStart); + assertTrue(client2.mStarted); verify(callback2).onClientStarted(eq(client2)); } @@ -187,16 +192,18 @@ public class BiometricSchedulerTest { // Schedule a BiometricPrompt authentication request mScheduler.scheduleClientMonitor(client1, callback1); - assertEquals(Operation.STATE_WAITING_FOR_COOKIE, mScheduler.mCurrentOperation.mState); - assertEquals(client1, mScheduler.mCurrentOperation.mClientMonitor); + assertNotEquals(0, mScheduler.mCurrentOperation.isReadyToStart()); + assertEquals(client1, mScheduler.mCurrentOperation.getClientMonitor()); assertEquals(0, mScheduler.mPendingOperations.size()); // Request it to be canceled. The operation can be canceled immediately, and the scheduler // should go back to idle, since in this case the framework has not even requested the HAL // to authenticate yet. mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */); + waitForIdle(); assertTrue(client1.isAlreadyDone()); assertTrue(client1.mDestroyed); + assertFalse(client1.mStartedHal); assertNull(mScheduler.mCurrentOperation); } @@ -210,8 +217,8 @@ public class BiometricSchedulerTest { // assertEquals(0, bsp.recentOperations.length); // Pretend the scheduler is busy enrolling, and check the proto dump again. - final TestClientMonitor2 client = new TestClientMonitor2(mContext, mToken, - () -> mock(Object.class), BiometricsProto.CM_ENROLL); + final TestHalClientMonitor client = new TestHalClientMonitor(mContext, mToken, + () -> mock(Object.class), 0, BiometricsProto.CM_ENROLL); mScheduler.scheduleClientMonitor(client); waitForIdle(); bsp = getDump(true /* clearSchedulerBuffer */); @@ -230,8 +237,8 @@ public class BiometricSchedulerTest { @Test public void testProtoDump_fifo() throws Exception { // Add the first operation - final TestClientMonitor2 client = new TestClientMonitor2(mContext, mToken, - () -> mock(Object.class), BiometricsProto.CM_ENROLL); + final TestHalClientMonitor client = new TestHalClientMonitor(mContext, mToken, + () -> mock(Object.class), 0, BiometricsProto.CM_ENROLL); mScheduler.scheduleClientMonitor(client); waitForIdle(); BiometricSchedulerProto bsp = getDump(false /* clearSchedulerBuffer */); @@ -244,8 +251,8 @@ public class BiometricSchedulerTest { client.getCallback().onClientFinished(client, true); // Add another operation - final TestClientMonitor2 client2 = new TestClientMonitor2(mContext, mToken, - () -> mock(Object.class), BiometricsProto.CM_REMOVE); + final TestHalClientMonitor client2 = new TestHalClientMonitor(mContext, mToken, + () -> mock(Object.class), 0, BiometricsProto.CM_REMOVE); mScheduler.scheduleClientMonitor(client2); waitForIdle(); bsp = getDump(false /* clearSchedulerBuffer */); @@ -256,8 +263,8 @@ public class BiometricSchedulerTest { client2.getCallback().onClientFinished(client2, true); // And another operation - final TestClientMonitor2 client3 = new TestClientMonitor2(mContext, mToken, - () -> mock(Object.class), BiometricsProto.CM_AUTHENTICATE); + final TestHalClientMonitor client3 = new TestHalClientMonitor(mContext, mToken, + () -> mock(Object.class), 0, BiometricsProto.CM_AUTHENTICATE); mScheduler.scheduleClientMonitor(client3); waitForIdle(); bsp = getDump(false /* clearSchedulerBuffer */); @@ -290,8 +297,7 @@ public class BiometricSchedulerTest { @Test public void testCancelPendingAuth() throws RemoteException { final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class); - - final TestClientMonitor client1 = new TestClientMonitor(mContext, mToken, lazyDaemon); + final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, lazyDaemon); final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class); final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon, mToken, callback); @@ -302,14 +308,12 @@ public class BiometricSchedulerTest { waitForIdle(); assertEquals(mScheduler.getCurrentClient(), client1); - assertEquals(Operation.STATE_WAITING_IN_QUEUE, - mScheduler.mPendingOperations.getFirst().mState); + assertFalse(mScheduler.mPendingOperations.getFirst().isStarted()); // Request cancel before the authentication client has started mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */); waitForIdle(); - assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING, - mScheduler.mPendingOperations.getFirst().mState); + assertTrue(mScheduler.mPendingOperations.getFirst().isMarkedCanceling()); // Finish the blocking client. The authentication client should send ERROR_CANCELED client1.getCallback().onClientFinished(client1, true /* success */); @@ -326,67 +330,109 @@ public class BiometricSchedulerTest { @Test public void testCancels_whenAuthRequestIdNotSet() { - testCancelsWhenRequestId(null /* requestId */, 2, true /* started */); + testCancelsAuthDetectWhenRequestId(null /* requestId */, 2, true /* started */); } @Test public void testCancels_whenAuthRequestIdNotSet_notStarted() { - testCancelsWhenRequestId(null /* requestId */, 2, false /* started */); + testCancelsAuthDetectWhenRequestId(null /* requestId */, 2, false /* started */); } @Test public void testCancels_whenAuthRequestIdMatches() { - testCancelsWhenRequestId(200L, 200, true /* started */); + testCancelsAuthDetectWhenRequestId(200L, 200, true /* started */); } @Test public void testCancels_whenAuthRequestIdMatches_noStarted() { - testCancelsWhenRequestId(200L, 200, false /* started */); + testCancelsAuthDetectWhenRequestId(200L, 200, false /* started */); } @Test public void testDoesNotCancel_whenAuthRequestIdMismatched() { - testCancelsWhenRequestId(10L, 20, true /* started */); + testCancelsAuthDetectWhenRequestId(10L, 20, true /* started */); } @Test public void testDoesNotCancel_whenAuthRequestIdMismatched_notStarted() { - testCancelsWhenRequestId(10L, 20, false /* started */); + testCancelsAuthDetectWhenRequestId(10L, 20, false /* started */); } - private void testCancelsWhenRequestId(@Nullable Long requestId, long cancelRequestId, + private void testCancelsAuthDetectWhenRequestId(@Nullable Long requestId, long cancelRequestId, boolean started) { - final boolean matches = requestId == null || requestId == cancelRequestId; final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class); final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class); - final TestAuthenticationClient client = new TestAuthenticationClient( - mContext, lazyDaemon, mToken, callback); + testCancelsWhenRequestId(requestId, cancelRequestId, started, + new TestAuthenticationClient(mContext, lazyDaemon, mToken, callback)); + } + + @Test + public void testCancels_whenEnrollRequestIdNotSet() { + testCancelsEnrollWhenRequestId(null /* requestId */, 2, false /* started */); + } + + @Test + public void testCancels_whenEnrollRequestIdMatches() { + testCancelsEnrollWhenRequestId(200L, 200, false /* started */); + } + + @Test + public void testDoesNotCancel_whenEnrollRequestIdMismatched() { + testCancelsEnrollWhenRequestId(10L, 20, false /* started */); + } + + private void testCancelsEnrollWhenRequestId(@Nullable Long requestId, long cancelRequestId, + boolean started) { + final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class); + final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class); + testCancelsWhenRequestId(requestId, cancelRequestId, started, + new TestEnrollClient(mContext, lazyDaemon, mToken, callback)); + } + + private void testCancelsWhenRequestId(@Nullable Long requestId, long cancelRequestId, + boolean started, HalClientMonitor<?> client) { + final boolean matches = requestId == null || requestId == cancelRequestId; if (requestId != null) { client.setRequestId(requestId); } + final boolean isAuth = client instanceof TestAuthenticationClient; + final boolean isEnroll = client instanceof TestEnrollClient; + mScheduler.scheduleClientMonitor(client); if (started) { mScheduler.startPreparedClient(client.getCookie()); } waitForIdle(); - mScheduler.cancelAuthenticationOrDetection(mToken, cancelRequestId); + if (isAuth) { + mScheduler.cancelAuthenticationOrDetection(mToken, cancelRequestId); + } else if (isEnroll) { + mScheduler.cancelEnrollment(mToken, cancelRequestId); + } else { + fail("unexpected operation type"); + } waitForIdle(); - assertEquals(matches && started ? 1 : 0, client.mNumCancels); + if (isAuth) { + // auth clients that were waiting for cookie when canceled should never invoke the hal + final TestAuthenticationClient authClient = (TestAuthenticationClient) client; + assertEquals(matches && started ? 1 : 0, authClient.mNumCancels); + assertEquals(started, authClient.mStartedHal); + } else if (isEnroll) { + final TestEnrollClient enrollClient = (TestEnrollClient) client; + assertEquals(matches ? 1 : 0, enrollClient.mNumCancels); + assertTrue(enrollClient.mStartedHal); + } if (matches) { - if (started) { - assertEquals(Operation.STATE_STARTED_CANCELING, - mScheduler.mCurrentOperation.mState); + if (started || isEnroll) { // prep'd auth clients and enroll clients + assertTrue(mScheduler.mCurrentOperation.isCanceling()); } } else { - if (started) { - assertEquals(Operation.STATE_STARTED, - mScheduler.mCurrentOperation.mState); + if (started || isEnroll) { // prep'd auth clients and enroll clients + assertTrue(mScheduler.mCurrentOperation.isStarted()); } else { - assertEquals(Operation.STATE_WAITING_FOR_COOKIE, - mScheduler.mCurrentOperation.mState); + assertNotEquals(0, mScheduler.mCurrentOperation.isReadyToStart()); } } } @@ -411,18 +457,14 @@ public class BiometricSchedulerTest { mScheduler.cancelAuthenticationOrDetection(mToken, 9999); waitForIdle(); - assertEquals(Operation.STATE_STARTED, - mScheduler.mCurrentOperation.mState); - assertEquals(Operation.STATE_WAITING_IN_QUEUE, - mScheduler.mPendingOperations.getFirst().mState); + assertTrue(mScheduler.mCurrentOperation.isStarted()); + assertFalse(mScheduler.mPendingOperations.getFirst().isStarted()); mScheduler.cancelAuthenticationOrDetection(mToken, requestId2); waitForIdle(); - assertEquals(Operation.STATE_STARTED, - mScheduler.mCurrentOperation.mState); - assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING, - mScheduler.mPendingOperations.getFirst().mState); + assertTrue(mScheduler.mCurrentOperation.isStarted()); + assertTrue(mScheduler.mPendingOperations.getFirst().isMarkedCanceling()); } @Test @@ -459,12 +501,12 @@ public class BiometricSchedulerTest { @Test public void testClientDestroyed_afterFinish() { final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class); - final TestClientMonitor client = - new TestClientMonitor(mContext, mToken, nonNullDaemon); + final TestHalClientMonitor client = + new TestHalClientMonitor(mContext, mToken, nonNullDaemon); mScheduler.scheduleClientMonitor(client); client.mCallback.onClientFinished(client, true /* success */); waitForIdle(); - assertTrue(client.wasDestroyed()); + assertTrue(client.mDestroyed); } private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception { @@ -472,8 +514,10 @@ public class BiometricSchedulerTest { } private static class TestAuthenticationClient extends AuthenticationClient<Object> { - int mNumCancels = 0; + boolean mStartedHal = false; + boolean mStoppedHal = false; boolean mDestroyed = false; + int mNumCancels = 0; public TestAuthenticationClient(@NonNull Context context, @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token, @@ -488,18 +532,16 @@ public class BiometricSchedulerTest { @Override protected void stopHalOperation() { - + mStoppedHal = true; } @Override protected void startHalOperation() { - + mStartedHal = true; } @Override - protected void handleLifecycleAfterAuth(boolean authenticated) { - - } + protected void handleLifecycleAfterAuth(boolean authenticated) {} @Override public boolean wasUserDetected() { @@ -519,36 +561,59 @@ public class BiometricSchedulerTest { } } - private static class TestClientMonitor2 extends TestClientMonitor { - private final int mProtoEnum; + private static class TestEnrollClient extends EnrollClient<Object> { + boolean mStartedHal = false; + boolean mStoppedHal = false; + int mNumCancels = 0; - public TestClientMonitor2(@NonNull Context context, @NonNull IBinder token, - @NonNull LazyDaemon<Object> lazyDaemon, int protoEnum) { - super(context, token, lazyDaemon); - mProtoEnum = protoEnum; + TestEnrollClient(@NonNull Context context, + @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token, + @NonNull ClientMonitorCallbackConverter listener) { + super(context, lazyDaemon, token, listener, 0 /* userId */, new byte[69], + "test" /* owner */, mock(BiometricUtils.class), + 5 /* timeoutSec */, 0 /* statsModality */, TEST_SENSOR_ID, + true /* shouldVibrate */); } @Override - public int getProtoEnum() { - return mProtoEnum; + protected void stopHalOperation() { + mStoppedHal = true; + } + + @Override + protected void startHalOperation() { + mStartedHal = true; + } + + @Override + protected boolean hasReachedEnrollmentLimit() { + return false; + } + + @Override + public void cancel() { + mNumCancels++; + super.cancel(); } } - private static class TestClientMonitor extends HalClientMonitor<Object> { + private static class TestHalClientMonitor extends HalClientMonitor<Object> { + private final int mProtoEnum; private boolean mUnableToStart; private boolean mStarted; private boolean mDestroyed; - public TestClientMonitor(@NonNull Context context, @NonNull IBinder token, + TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token, @NonNull LazyDaemon<Object> lazyDaemon) { - this(context, token, lazyDaemon, 0 /* cookie */); + this(context, token, lazyDaemon, 0 /* cookie */, BiometricsProto.CM_UPDATE_ACTIVE_USER); } - public TestClientMonitor(@NonNull Context context, @NonNull IBinder token, - @NonNull LazyDaemon<Object> lazyDaemon, int cookie) { + TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token, + @NonNull LazyDaemon<Object> lazyDaemon, int cookie, int protoEnum) { super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */, TAG, cookie, TEST_SENSOR_ID, 0 /* statsModality */, 0 /* statsAction */, 0 /* statsClient */); + mProtoEnum = protoEnum; } @Override @@ -559,9 +624,7 @@ public class BiometricSchedulerTest { @Override public int getProtoEnum() { - // Anything other than CM_NONE, which is used to represent "idle". Tests that need - // real proto enums should use TestClientMonitor2 - return BiometricsProto.CM_UPDATE_ACTIVE_USER; + return mProtoEnum; } @Override @@ -573,7 +636,7 @@ public class BiometricSchedulerTest { @Override protected void startHalOperation() { - + mStarted = true; } @Override @@ -581,22 +644,9 @@ public class BiometricSchedulerTest { super.destroy(); mDestroyed = true; } - - public boolean wasUnableToStart() { - return mUnableToStart; - } - - public boolean hasStarted() { - return mStarted; - } - - public boolean wasDestroyed() { - return mDestroyed; - } - } - private static void waitForIdle() { - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + private void waitForIdle() { + TestableLooper.get(this).processAllMessages(); } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java index 7fccd49db04b..407f5fb04adf 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors; +import static android.testing.TestableLooper.RunWithLooper; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; @@ -28,52 +30,53 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.biometrics.IBiometricService; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @Presubmit +@RunWith(AndroidTestingRunner.class) +@RunWithLooper @SmallTest public class UserAwareBiometricSchedulerTest { - private static final String TAG = "BiometricSchedulerTest"; + private static final String TAG = "UserAwareBiometricSchedulerTest"; private static final int TEST_SENSOR_ID = 0; + private Handler mHandler; private UserAwareBiometricScheduler mScheduler; - private IBinder mToken; + private IBinder mToken = new Binder(); @Mock private Context mContext; @Mock private IBiometricService mBiometricService; - private TestUserStartedCallback mUserStartedCallback; - private TestUserStoppedCallback mUserStoppedCallback; + private TestUserStartedCallback mUserStartedCallback = new TestUserStartedCallback(); + private TestUserStoppedCallback mUserStoppedCallback = new TestUserStoppedCallback(); private int mCurrentUserId = UserHandle.USER_NULL; - private boolean mStartOperationsFinish; - private int mStartUserClientCount; + private boolean mStartOperationsFinish = true; + private int mStartUserClientCount = 0; @Before public void setUp() { MockitoAnnotations.initMocks(this); - - mToken = new Binder(); - mStartOperationsFinish = true; - mStartUserClientCount = 0; - mUserStartedCallback = new TestUserStartedCallback(); - mUserStoppedCallback = new TestUserStoppedCallback(); - + mHandler = new Handler(TestableLooper.get(this).getLooper()); mScheduler = new UserAwareBiometricScheduler(TAG, + mHandler, BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityDispatcher */, mBiometricService, @@ -117,7 +120,7 @@ public class UserAwareBiometricSchedulerTest { mCurrentUserId = UserHandle.USER_NULL; mStartOperationsFinish = false; - final BaseClientMonitor[] nextClients = new BaseClientMonitor[] { + final BaseClientMonitor[] nextClients = new BaseClientMonitor[]{ mock(BaseClientMonitor.class), mock(BaseClientMonitor.class), mock(BaseClientMonitor.class) @@ -147,11 +150,11 @@ public class UserAwareBiometricSchedulerTest { waitForIdle(); final TestStartUserClient startUserClient = - (TestStartUserClient) mScheduler.mCurrentOperation.mClientMonitor; + (TestStartUserClient) mScheduler.mCurrentOperation.getClientMonitor(); mScheduler.reset(); assertNull(mScheduler.mCurrentOperation); - final BiometricScheduler.Operation fakeOperation = new BiometricScheduler.Operation( + final BiometricSchedulerOperation fakeOperation = new BiometricSchedulerOperation( mock(BaseClientMonitor.class), new BaseClientMonitor.Callback() {}); mScheduler.mCurrentOperation = fakeOperation; startUserClient.mCallback.onClientFinished(startUserClient, true); @@ -194,8 +197,8 @@ public class UserAwareBiometricSchedulerTest { verify(nextClient).start(any()); } - private static void waitForIdle() { - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + private void waitForIdle() { + TestableLooper.get(this).processAllMessages(); } private class TestUserStoppedCallback implements StopUserClient.UserStoppedCallback { diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java index a13dff21439d..2718bf90d857 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java @@ -33,6 +33,7 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import com.android.server.biometrics.sensors.BiometricScheduler; +import com.android.server.biometrics.sensors.CoexCoordinator; import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; @@ -79,10 +80,13 @@ public class SensorTest { when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService); mScheduler = new UserAwareBiometricScheduler(TAG, + new Handler(mLooper.getLooper()), BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */, + mBiometricService, () -> USER_ID, - mUserSwitchCallback); + mUserSwitchCallback, + CoexCoordinator.getInstance()); mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()), TAG, mScheduler, SENSOR_ID, USER_ID, mLockoutCache, mLockoutResetDispatcher, mHalSessionCallback); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java index 39c51d5f5e5e..21a7a8ae65b9 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java @@ -32,7 +32,9 @@ import android.hardware.face.FaceSensorProperties; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.face.IFaceServiceReceiver; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.UserManager; import android.platform.test.annotations.Presubmit; @@ -69,6 +71,7 @@ public class Face10Test { @Mock private BiometricScheduler mScheduler; + private final Handler mHandler = new Handler(Looper.getMainLooper()); private LockoutResetDispatcher mLockoutResetDispatcher; private com.android.server.biometrics.sensors.face.hidl.Face10 mFace10; private IBinder mBinder; @@ -97,7 +100,7 @@ public class Face10Test { resetLockoutRequiresChallenge); Face10.sSystemClock = Clock.fixed(Instant.ofEpochMilli(100), ZoneId.of("PST")); - mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mScheduler); + mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mHandler, mScheduler); mBinder = new Binder(); } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java index 0d520ca9a4e4..d4609b55afba 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java @@ -33,6 +33,7 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import com.android.server.biometrics.sensors.BiometricScheduler; +import com.android.server.biometrics.sensors.CoexCoordinator; import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; @@ -79,10 +80,13 @@ public class SensorTest { when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService); mScheduler = new UserAwareBiometricScheduler(TAG, + new Handler(mLooper.getLooper()), BiometricScheduler.SENSOR_TYPE_FP_OTHER, null /* gestureAvailabilityDispatcher */, + mBiometricService, () -> USER_ID, - mUserSwitchCallback); + mUserSwitchCallback, + CoexCoordinator.getInstance()); mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()), TAG, mScheduler, SENSOR_ID, USER_ID, mLockoutCache, mLockoutResetDispatcher, mHalSessionCallback); |