diff options
135 files changed, 6303 insertions, 1927 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index e17fbede3da8..a1375d79846d 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -38,6 +38,7 @@ aconfig_srcjars = [ ":android.permission.flags-aconfig-java{.generated_srcjars}", ":android.database.sqlite-aconfig-java{.generated_srcjars}", ":hwui_flags_java_lib{.generated_srcjars}", + ":framework_graphics_flags_java_lib{.generated_srcjars}", ":display_flags_lib{.generated_srcjars}", ":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}", ":android.multiuser.flags-aconfig-java{.generated_srcjars}", @@ -46,6 +47,7 @@ aconfig_srcjars = [ ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}", ":android.service.voice.flags-aconfig-java{.generated_srcjars}", ":android.service.autofill.flags-aconfig-java{.generated_srcjars}", + ":com.android.net.flags-aconfig-java{.generated_srcjars}", ] filegroup { @@ -366,6 +368,12 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +java_aconfig_library { + name: "framework_graphics_flags_java_lib", + aconfig_declarations: "framework_graphics_flags", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Display java_aconfig_library { name: "display_flags_lib", @@ -485,3 +493,10 @@ java_aconfig_library { aconfig_declarations: "android.companion.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// CoreNetworking +java_aconfig_library { + name: "com.android.net.flags-aconfig-java", + aconfig_declarations: "com.android.net.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig index 4e3cb7d83451..3e835b8ad429 100644 --- a/apex/jobscheduler/service/aconfig/job.aconfig +++ b/apex/jobscheduler/service/aconfig/job.aconfig @@ -2,7 +2,7 @@ package: "com.android.server.job" flag { name: "relax_prefetch_connectivity_constraint_only_on_charger" - namespace: "backstagepower" + namespace: "backstage_power" description: "Only relax a prefetch job's connectivity constraint when the device is charging" bug: "299329948" }
\ No newline at end of file diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 3cbee5d07bb9..384a480af4e9 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -173,8 +173,6 @@ import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; import dalvik.annotation.optimization.NeverCompile; -import libcore.util.EmptyArray; - import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -260,7 +258,7 @@ public class AlarmManagerService extends SystemService { /** * A map from uid to the last op-mode we have seen for * {@link AppOpsManager#OP_SCHEDULE_EXACT_ALARM}. Used for evaluating permission state change - * when the denylist changes. + * when the app-op changes. */ @VisibleForTesting @GuardedBy("mLock") @@ -671,9 +669,6 @@ public class AlarmManagerService extends SystemService { @VisibleForTesting final class Constants implements DeviceConfig.OnPropertiesChangedListener, EconomyManagerInternal.TareStateChangeListener { - @VisibleForTesting - static final int MAX_EXACT_ALARM_DENY_LIST_SIZE = 250; - // Key names stored in the settings value. @VisibleForTesting static final String KEY_MIN_FUTURITY = "min_futurity"; @@ -727,8 +722,6 @@ public class AlarmManagerService extends SystemService { @VisibleForTesting static final String KEY_PRIORITY_ALARM_DELAY = "priority_alarm_delay"; @VisibleForTesting - static final String KEY_EXACT_ALARM_DENY_LIST = "exact_alarm_deny_list"; - @VisibleForTesting static final String KEY_MIN_DEVICE_IDLE_FUZZ = "min_device_idle_fuzz"; @VisibleForTesting static final String KEY_MAX_DEVICE_IDLE_FUZZ = "max_device_idle_fuzz"; @@ -835,13 +828,6 @@ public class AlarmManagerService extends SystemService { public long PRIORITY_ALARM_DELAY = DEFAULT_PRIORITY_ALARM_DELAY; /** - * Read-only set of apps that won't get SCHEDULE_EXACT_ALARM when the app-op mode for - * OP_SCHEDULE_EXACT_ALARM is MODE_DEFAULT. Since this is read-only and volatile, this can - * be accessed without synchronizing on {@link #mLock}. - */ - public volatile Set<String> EXACT_ALARM_DENY_LIST = Collections.emptySet(); - - /** * Minimum time interval that an IDLE_UNTIL will be pulled earlier to a subsequent * WAKE_FROM_IDLE alarm. */ @@ -1025,21 +1011,6 @@ public class AlarmManagerService extends SystemService { PRIORITY_ALARM_DELAY = properties.getLong(KEY_PRIORITY_ALARM_DELAY, DEFAULT_PRIORITY_ALARM_DELAY); break; - case KEY_EXACT_ALARM_DENY_LIST: - final String rawValue = properties.getString(KEY_EXACT_ALARM_DENY_LIST, - ""); - final String[] values = rawValue.isEmpty() - ? EmptyArray.STRING - : rawValue.split(",", MAX_EXACT_ALARM_DENY_LIST_SIZE + 1); - if (values.length > MAX_EXACT_ALARM_DENY_LIST_SIZE) { - Slog.w(TAG, "Deny list too long, truncating to " - + MAX_EXACT_ALARM_DENY_LIST_SIZE + " elements."); - updateExactAlarmDenyList( - Arrays.copyOf(values, MAX_EXACT_ALARM_DENY_LIST_SIZE)); - } else { - updateExactAlarmDenyList(values); - } - break; case KEY_MIN_DEVICE_IDLE_FUZZ: case KEY_MAX_DEVICE_IDLE_FUZZ: if (!deviceIdleFuzzBoundariesUpdated) { @@ -1110,28 +1081,6 @@ public class AlarmManagerService extends SystemService { } } - private void updateExactAlarmDenyList(String[] newDenyList) { - final Set<String> newSet = Collections.unmodifiableSet(new ArraySet<>(newDenyList)); - final Set<String> removed = new ArraySet<>(EXACT_ALARM_DENY_LIST); - final Set<String> added = new ArraySet<>(newDenyList); - - added.removeAll(EXACT_ALARM_DENY_LIST); - removed.removeAll(newSet); - if (added.size() > 0) { - mHandler.obtainMessage(AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_ADDED, added) - .sendToTarget(); - } - if (removed.size() > 0) { - mHandler.obtainMessage(AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED, removed) - .sendToTarget(); - } - if (newDenyList.length == 0) { - EXACT_ALARM_DENY_LIST = Collections.emptySet(); - } else { - EXACT_ALARM_DENY_LIST = newSet; - } - } - private void updateDeviceIdleFuzzBoundaries() { final DeviceConfig.Properties properties = DeviceConfig.getProperties( DeviceConfig.NAMESPACE_ALARM_MANAGER, @@ -1277,9 +1226,6 @@ public class AlarmManagerService extends SystemService { TimeUtils.formatDuration(PRIORITY_ALARM_DELAY, pw); pw.println(); - pw.print(KEY_EXACT_ALARM_DENY_LIST, EXACT_ALARM_DENY_LIST); - pw.println(); - pw.print(KEY_MIN_DEVICE_IDLE_FUZZ); pw.print("="); TimeUtils.formatDuration(MIN_DEVICE_IDLE_FUZZ, pw); @@ -2114,14 +2060,10 @@ public class AlarmManagerService extends SystemService { ? permissionState : (newMode == AppOpsManager.MODE_ALLOWED); } else { - final boolean allowedByDefault = - !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName); hadPermission = (oldMode == AppOpsManager.MODE_DEFAULT) - ? allowedByDefault - : (oldMode == AppOpsManager.MODE_ALLOWED); + || (oldMode == AppOpsManager.MODE_ALLOWED); hasPermission = (newMode == AppOpsManager.MODE_DEFAULT) - ? allowedByDefault - : (newMode == AppOpsManager.MODE_ALLOWED); + || (newMode == AppOpsManager.MODE_ALLOWED); } if (hadPermission && !hasPermission) { @@ -2769,11 +2711,8 @@ public class AlarmManagerService extends SystemService { // Compatibility permission check for older apps. final int mode = mAppOps.checkOpNoThrow(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, uid, packageName); - if (mode == AppOpsManager.MODE_DEFAULT) { - hasPermission = !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName); - } else { - hasPermission = (mode == AppOpsManager.MODE_ALLOWED); - } + hasPermission = (mode == AppOpsManager.MODE_DEFAULT) + || (mode == AppOpsManager.MODE_ALLOWED); } mStatLogger.logDurationStat(Stats.HAS_SCHEDULE_EXACT_ALARM, start); return hasPermission; @@ -3993,63 +3932,6 @@ public class AlarmManagerService extends SystemService { } /** - * Called when the {@link Constants#EXACT_ALARM_DENY_LIST}, changes with the packages that - * either got added or deleted. - * These packages may lose or gain the SCHEDULE_EXACT_ALARM permission. - * - * Note that these packages don't need to be installed on the device, but if they are and they - * do undergo a permission change, we will handle them appropriately. - * - * This should not be called with the lock held as it calls out to other services. - * This is not expected to get called frequently. - */ - void handleChangesToExactAlarmDenyList(ArraySet<String> changedPackages, boolean added) { - Slog.w(TAG, "Packages " + changedPackages + (added ? " added to" : " removed from") - + " the exact alarm deny list."); - - final int[] startedUserIds = mActivityManagerInternal.getStartedUserIds(); - - for (int i = 0; i < changedPackages.size(); i++) { - final String changedPackage = changedPackages.valueAt(i); - for (final int userId : startedUserIds) { - final int uid = mPackageManagerInternal.getPackageUid(changedPackage, 0, userId); - if (uid <= 0) { - continue; - } - if (!isExactAlarmChangeEnabled(changedPackage, userId)) { - continue; - } - if (isScheduleExactAlarmDeniedByDefault(changedPackage, userId)) { - continue; - } - if (hasUseExactAlarmInternal(changedPackage, uid)) { - continue; - } - if (!mExactAlarmCandidates.contains(UserHandle.getAppId(uid))) { - // Permission isn't requested, deny list doesn't matter. - continue; - } - final int appOpMode; - synchronized (mLock) { - appOpMode = mLastOpScheduleExactAlarm.get(uid, - AppOpsManager.opToDefaultMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM)); - } - if (appOpMode != AppOpsManager.MODE_DEFAULT) { - // Deny list doesn't matter. - continue; - } - // added: true => package was added to the deny list - // added: false => package was removed from the deny list - if (added) { - removeExactAlarmsOnPermissionRevoked(uid, changedPackage, /*killUid = */ true); - } else { - sendScheduleExactAlarmPermissionStateChangedBroadcast(changedPackage, userId); - } - } - } - } - - /** * Called when an app loses the permission to use exact alarms. This will happen when the app * no longer has either {@link Manifest.permission#SCHEDULE_EXACT_ALARM} or * {@link Manifest.permission#USE_EXACT_ALARM}. @@ -4931,8 +4813,8 @@ public class AlarmManagerService extends SystemService { public static final int CHARGING_STATUS_CHANGED = 6; public static final int REMOVE_FOR_CANCELED = 7; public static final int REMOVE_EXACT_ALARMS = 8; - public static final int EXACT_ALARM_DENY_LIST_PACKAGES_ADDED = 9; - public static final int EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED = 10; + // Unused id 9 + // Unused id 10 public static final int REFRESH_EXACT_ALARM_CANDIDATES = 11; public static final int TARE_AFFORDABILITY_CHANGED = 12; public static final int CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE = 13; @@ -5041,12 +4923,6 @@ public class AlarmManagerService extends SystemService { String packageName = (String) msg.obj; removeExactAlarmsOnPermissionRevoked(uid, packageName, /*killUid = */true); break; - case EXACT_ALARM_DENY_LIST_PACKAGES_ADDED: - handleChangesToExactAlarmDenyList((ArraySet<String>) msg.obj, true); - break; - case EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED: - handleChangesToExactAlarmDenyList((ArraySet<String>) msg.obj, false); - break; case REFRESH_EXACT_ALARM_CANDIDATES: refreshExactAlarmCandidates(); break; diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline index bfd2e28ba0bf..c28480e4f11a 100644 --- a/api/javadoc-lint-baseline +++ b/api/javadoc-lint-baseline @@ -195,33 +195,7 @@ android/service/quickaccesswallet/WalletCard.java:285: lint: Unresolved link/see android/service/voice/VoiceInteractionSession.java:293: lint: Unresolved link/see tag "android.service.voice.VoiceInteractionService#KEY_SHOW_SESSION_ID VoiceInteractionService#KEY_SHOW_SESSION_ID" in android.service.voice.VoiceInteractionSession [101] android/text/DynamicLayout.java:141: lint: Unresolved link/see tag "LineBreakconfig" in android.text.DynamicLayout [101] android/text/WordSegmentFinder.java:13: lint: Unresolved link/see tag "android.text.method.WordIterator WordIterator" in android.text.WordSegmentFinder [101] -android/view/InputDevice.java:71: lint: Unresolved link/see tag "InputManagerGlobal.InputDeviceListener" in android.view.InputDevice [101] android/view/PixelCopy.java:468: lint: Unresolved link/see tag "android.view.PixelCopy.CopyResultStatus CopyResultStatus" in android.view.PixelCopy.Result [101] -android/view/ScrollFeedbackProvider.java:-25: lint: Unresolved link/see tag "InputManager" in android.view.ScrollFeedbackProvider [101] -android/view/ScrollFeedbackProvider.java:-25: lint: Unresolved link/see tag "InputManager#getInputDeviceIds()" in android.view.ScrollFeedbackProvider [101] -android/view/SurfaceControl.java:823: lint: Unresolved link/see tag "android.view.SurfaceControl.TrustedPresentationCallback TrustedPresentationCallback" in android.view.SurfaceControl.Transaction [101] -android/view/SurfaceControl.java:900: lint: Unresolved link/see tag "android.view.SurfaceControl.TrustedPresentationCallback TrustedPresentationCallback" in android.view.SurfaceControl.Transaction [101] -android/view/SurfaceControl.java:908: lint: Unresolved link/see tag "android.view.SurfaceControl.TrustedPresentationCallback TrustedPresentationCallback" in android.view.SurfaceControl.Transaction [101] -android/view/View.java:1647: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#setAccessibilityPaneTitle(View, CharSequence)" in android.view.View [101] -android/view/View.java:4669: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#setScreenReaderFocusable(View, boolean)" in android.view.View [101] -android/view/View.java:4712: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#setAccessibilityHeading(View, boolean)" in android.view.View [101] -android/view/WindowManager.java:230: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowManager [101] -android/view/WindowManager.java:247: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowManager [101] -android/view/WindowManager.java:822: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106] -android/view/WindowManager.java:832: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106] -android/view/WindowMetrics.java:22: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101] -android/view/WindowMetrics.java:57: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101] -android/view/WindowMetrics.java:114: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101] -android/view/WindowMetrics.java:127: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101] -android/view/accessibility/AccessibilityNodeInfo.java:368: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#addAccessibilityAction(View, AccessibilityNodeInfoCompat.AccessibilityActionCompat)" in android.view.accessibility.AccessibilityNodeInfo [101] -android/view/accessibility/AccessibilityNodeInfo.java:3246: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#addAccessibilityAction(View, AccessibilityNodeInfoCompat.AccessibilityActionCompat)" in android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction [101] -android/view/displayhash/DisplayHashResultCallback.java:38: lint: Unresolved link/see tag "android.view.displayhash.DisplayHashResultCallback.DisplayHashErrorCode DisplayHashErrorCode" in android.view.displayhash.DisplayHashResultCallback [101] -android/view/inputmethod/EditorInfo.java:107: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.EditorInfo [101] -android/view/inputmethod/EditorInfo.java:122: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.EditorInfo [101] -android/view/inputmethod/InputMethodManager.java:423: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.InputMethodManager [101] -android/view/inputmethod/InputMethodManager.java:447: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.InputMethodManager [101] -android/view/inputmethod/InputMethodManager.java:456: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.InputMethodManager [101] -android/view/inspector/PropertyReader.java:141: lint: Unresolved link/see tag "android.annotation.ColorInt ColorInt" in android.view.inspector.PropertyReader [101] android/window/BackEvent.java:24: lint: Unresolved link/see tag "android.window.BackMotionEvent BackMotionEvent" in android.window.BackEvent [101] android/net/wifi/SoftApConfiguration.java:173: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setShutdownTimeoutMillis(long)" in android.net.wifi.SoftApConfiguration [101] @@ -232,8 +206,6 @@ android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/se android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "#STATE_ERROR" in android [101] android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "Callback#onFailure" in android [101] android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "Callback#onUnknownFailure" in android [101] -android/view/animation/AnimationUtils.java:64: lint: Unresolved link/see tag "Build.VERSION_CODES#VANILLA_ICE_CREAM" in android.view.animation.AnimationUtils [101] -android/view/contentcapture/ContentCaptureSession.java:188: lint: Unresolved link/see tag "UPSIDE_DOWN_CAKE" in android.view.contentcapture.ContentCaptureSession [101] com/android/internal/policy/PhoneWindow.java:172: lint: Unresolved link/see tag "Build.VERSION_CODES#VANILLA_ICE_CREAM" in com.android.internal.policy.PhoneWindow [101] com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "IdentifierType#DAB_SID_EXT" in android [101] @@ -249,7 +221,5 @@ com/android/server/devicepolicy/DevicePolicyManagerService.java:860: lint: Unres android/os/BatteryStatsManager.java:260: lint: Invalid tag: @Deprecated [131] android/os/BatteryStatsManager.java:275: lint: Invalid tag: @Deprecated [131] -android/view/WindowManager.java:906: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106] -android/view/WindowManager.java:916: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106] java/lang/ClassLoader.java:853: lint: Unknown tag: @systemProperty [103] diff --git a/core/api/current.txt b/core/api/current.txt index 93071f7d5845..36cdd7fb84bb 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -15671,7 +15671,7 @@ package android.graphics { public final class Gainmap implements android.os.Parcelable { ctor public Gainmap(@NonNull android.graphics.Bitmap); - ctor public Gainmap(@NonNull android.graphics.Gainmap, @NonNull android.graphics.Bitmap); + ctor @FlaggedApi("com.android.graphics.hwui.flags.gainmap_constructor_with_metadata") public Gainmap(@NonNull android.graphics.Gainmap, @NonNull android.graphics.Bitmap); method public int describeContents(); method @NonNull public float getDisplayRatioForFullHdr(); method @NonNull public float[] getEpsilonHdr(); @@ -16309,7 +16309,7 @@ package android.graphics { method public void arcTo(float, float, float, float, float, float, boolean); method public void close(); method @Deprecated public void computeBounds(@NonNull android.graphics.RectF, boolean); - method public void computeBounds(@NonNull android.graphics.RectF); + method @FlaggedApi("com.android.graphics.flags.exact_compute_bounds") public void computeBounds(@NonNull android.graphics.RectF); method public void conicTo(float, float, float, float, float); method public void cubicTo(float, float, float, float, float, float); method @NonNull public android.graphics.Path.FillType getFillType(); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 7dcc7b2cab13..a1da9e02f23d 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -299,6 +299,7 @@ package android { field public static final String RECEIVE_DATA_ACTIVITY_CHANGE = "android.permission.RECEIVE_DATA_ACTIVITY_CHANGE"; field public static final String RECEIVE_DEVICE_CUSTOMIZATION_READY = "android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY"; field public static final String RECEIVE_EMERGENCY_BROADCAST = "android.permission.RECEIVE_EMERGENCY_BROADCAST"; + field @FlaggedApi("android.permission.flags.voice_activation_permission_apis") public static final String RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA = "android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA"; field @FlaggedApi("android.permission.flags.voice_activation_permission_apis") public static final String RECEIVE_SANDBOX_TRIGGER_AUDIO = "android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO"; field public static final String RECEIVE_WIFI_CREDENTIAL_CHANGE = "android.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE"; field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO"; diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 17637df90b99..ecbc9b1d52c2 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1487,13 +1487,13 @@ public class AppOpsManager { AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO; /** - * Allows the assistant app to get the training data from the trusted process to improve the - * hotword training model. + * Allows the privileged assistant app to receive the training data from the sandboxed hotword + * detection service. * * @hide */ - public static final int OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA = - AppProtoEnums.APP_OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA; + public static final int OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA = + AppProtoEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA; /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -1641,7 +1641,7 @@ public class AppOpsManager { OPSTR_CAMERA_SANDBOXED, OPSTR_RECORD_AUDIO_SANDBOXED, OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO, - OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA + OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA }) public @interface AppOpString {} @@ -2262,13 +2262,13 @@ public class AppOpsManager { "android:receive_sandbox_trigger_audio"; /** - * Allows the assistant app to get the training data from the trusted process to improve - * the hotword training model. + * Allows the privileged assistant app to receive training data from the sandboxed hotword + * detection service. * * @hide */ - public static final String OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA = - "android:receive_trusted_process_training_data"; + public static final String OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA = + "android:RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA"; /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; @@ -2381,7 +2381,8 @@ public class AppOpsManager { OP_FOREGROUND_SERVICE_SPECIAL_USE, OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD, OP_USE_FULL_SCREEN_INTENT, - OP_RECEIVE_SANDBOX_TRIGGER_AUDIO + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, + OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA }; static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{ @@ -2814,9 +2815,11 @@ public class AppOpsManager { "RECEIVE_SANDBOX_TRIGGER_AUDIO") .setPermission(Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO) .setDefaultMode(AppOpsManager.MODE_DEFAULT).build(), - new AppOpInfo.Builder(OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA, - OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA, - "RECEIVE_TRUSTED_PROCESS_TRAINING_DATA").build() + new AppOpInfo.Builder(OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA, + OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA, + "RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA") + .setPermission(Manifest.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA) + .setDefaultMode(AppOpsManager.MODE_DEFAULT).build() }; // The number of longs needed to form a full bitmask of app ops diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 45338bb2c0a2..4d5d05611d7a 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -4605,6 +4605,16 @@ public abstract class PackageManager { public static final String FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS = "android.software.wallet_location_based_suggestions"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has + * the rotary encoder hardware to support rotating bezel on watch. + * + * @hide + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_ROTARY_ENCODER_LOW_RES = + "android.hardware.rotaryencoder.lowres"; + /** @hide */ public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true; diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index c35b6907cd9d..9f886c826174 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -778,7 +778,8 @@ public final class InputDevice implements Parcelable { * Each gamepad or joystick is given a unique, positive controller number when initially * configured by the system. This number may change due to events such as device disconnects / * reconnects or user initiated reassignment. Any change in number will trigger an event that - * can be observed by registering an {@link InputManagerGlobal.InputDeviceListener}. + * can be observed by registering an + * {@link android.hardware.input.InputManager.InputDeviceListener}. * </p> * <p> * All input devices which are not gamepads or joysticks will be assigned a controller number diff --git a/core/java/android/view/ScrollFeedbackProvider.java b/core/java/android/view/ScrollFeedbackProvider.java index 78716f5646f6..0ba414817247 100644 --- a/core/java/android/view/ScrollFeedbackProvider.java +++ b/core/java/android/view/ScrollFeedbackProvider.java @@ -17,7 +17,6 @@ package android.view; import android.annotation.FlaggedApi; -import android.annotation.NonNull; import android.view.flags.Flags; /** @@ -43,7 +42,8 @@ import android.view.flags.Flags; * the scroll event. If calling this method in response to a {@link MotionEvent}, use the device ID * that is reported by the event, which can be obtained using {@link MotionEvent#getDeviceId()}. * Otherwise, use a valid ID that is obtained from {@link InputDevice#getId()}, or from an - * {@link InputManager} instance ({@link InputManager#getInputDeviceIds()} gives all the valid input + * {@link android.hardware.input.InputManager} instance + * ({@link android.hardware.input.InputManager#getInputDeviceIds()} gives all the valid input * device IDs). * * <li><p><b>source</b>: should always be the {@link InputDevice} source that generated the scroll diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index b080b7115728..2f3d73a36425 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -263,7 +263,7 @@ public final class SurfaceControl implements Parcelable { private static native void nativeSetDefaultFrameRateCompatibility(long transactionObj, long nativeObject, int compatibility); private static native void nativeSetFrameRateCategory( - long transactionObj, long nativeObject, int category); + long transactionObj, long nativeObject, int category, boolean smoothSwitchOnly); private static native void nativeSetFrameRateSelectionStrategy( long transactionObj, long nativeObject, int strategy); private static native long nativeGetHandle(long nativeObject); @@ -3709,6 +3709,10 @@ public final class SurfaceControl implements Parcelable { * @param sc The SurfaceControl to specify the frame rate category of. * @param category The frame rate category of this surface. The category value may influence * the system's choice of display frame rate. + * @param smoothSwitchOnly Set to {@code true} to indicate the display frame rate should not + * change if changing it would cause jank. Else {@code false}. + * This parameter is ignored when {@code category} is + * {@link Surface#FRAME_RATE_CATEGORY_DEFAULT}. * * @return This transaction object. * @@ -3717,10 +3721,10 @@ public final class SurfaceControl implements Parcelable { * @hide */ @NonNull - public Transaction setFrameRateCategory( - @NonNull SurfaceControl sc, @Surface.FrameRateCategory int category) { + public Transaction setFrameRateCategory(@NonNull SurfaceControl sc, + @Surface.FrameRateCategory int category, boolean smoothSwitchOnly) { checkPreconditions(sc); - nativeSetFrameRateCategory(mNativeObject, sc.mNativeObject, category); + nativeSetFrameRateCategory(mNativeObject, sc.mNativeObject, category, smoothSwitchOnly); return this; } @@ -4266,8 +4270,7 @@ public final class SurfaceControl implements Parcelable { * be somewhat arbitrary, and so there are some somewhat arbitrary decisions in * this API as well. * <p> - * @param sc The {@link SurfaceControl} to set the - * {@link TrustedPresentationCallback} on + * @param sc The {@link SurfaceControl} to set the callback on * @param thresholds The {@link TrustedPresentationThresholds} that will specify when the to * invoke the callback. * @param executor The {@link Executor} where the callback will be invoked on. @@ -4300,10 +4303,9 @@ public final class SurfaceControl implements Parcelable { } /** - * Clears the {@link TrustedPresentationCallback} for a specific {@link SurfaceControl} + * Clears the callback for a specific {@link SurfaceControl} * - * @param sc The SurfaceControl that the {@link TrustedPresentationCallback} should be - * cleared from + * @param sc The SurfaceControl that the callback should be cleared from * @return This transaction */ @NonNull diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 6c8f5424f85e..a3b93b433dda 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1685,7 +1685,7 @@ public interface WindowManager extends ViewManager { * orientation (e.g. with {@link android.app.Activity#setRequestedOrientation(int)}). This * listener gives application an opportunity to selectively react to device orientation changes. * The newly added listener will be called with current proposed rotation. Note that the context - * of this window manager instance must be a {@link android.annotation.UiContext}. + * of this window manager instance must be a {@code UiContext}. * * @param executor The executor on which callback method will be invoked. * @param listener Called when the proposed rotation for the context is being delivered. @@ -1693,7 +1693,7 @@ public interface WindowManager extends ViewManager { * {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180} and * {@link Surface#ROTATION_270}. * @throws UnsupportedOperationException if this method is called on an instance that is not - * associated with a {@link android.annotation.UiContext}. + * associated with a {@code UiContext}. */ default void addProposedRotationListener(@NonNull @CallbackExecutor Executor executor, @NonNull IntConsumer listener) { @@ -3115,7 +3115,7 @@ public interface WindowManager extends ViewManager { /** * Never animate position changes of the window. * - * @see android.R.attr#Window_windowNoMoveAnimation + * @see android.R.styleable#Window_windowNoMoveAnimation * {@hide} */ @UnsupportedAppUsage @@ -4531,7 +4531,7 @@ public interface WindowManager extends ViewManager { * Set whether animations can be played for position changes on this window. If disabled, * the window will move to its new position instantly without animating. * - * @attr ref android.R.attr#Window_windowNoMoveAnimation + * @attr ref android.R.styleable#Window_windowNoMoveAnimation */ public void setCanPlayMoveAnimation(boolean enable) { if (enable) { @@ -4546,7 +4546,7 @@ public interface WindowManager extends ViewManager { * This does not guarantee that an animation will be played in all such situations. For * example, drag-resizing may move the window but not play an animation. * - * @attr ref android.R.attr#Window_windowNoMoveAnimation + * @attr ref android.R.styleable#Window_windowNoMoveAnimation */ public boolean canPlayMoveAnimation() { return (privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0; diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java index 7ad43c76efaa..26298bc645ad 100644 --- a/core/java/android/view/WindowMetrics.java +++ b/core/java/android/view/WindowMetrics.java @@ -47,7 +47,6 @@ import java.util.function.Supplier; * @see WindowInsets#getInsets(int) * @see WindowManager#getCurrentWindowMetrics() * @see WindowManager#getMaximumWindowMetrics() - * @see android.annotation.UiContext */ public final class WindowMetrics { @NonNull @@ -99,8 +98,7 @@ public final class WindowMetrics { } /** - * Returns the bounds of the area associated with this window or - * {@link android.annotation.UiContext}. + * Returns the bounds of the area associated with this window or {@code UiContext}. * <p> * <b>Note that the size of the reported bounds can have different size than * {@link Display#getSize(Point)}.</b> This method reports the window size including all system @@ -133,7 +131,7 @@ public final class WindowMetrics { /** * Returns the {@link WindowInsets} of the area associated with this window or - * {@link android.annotation.UiContext}. + * {@code UiContext}. * * @return the {@link WindowInsets} of the visual area. */ @@ -146,9 +144,8 @@ public final class WindowMetrics { } /** - * Returns the density of the area associated with this window or - * {@link android.annotation.UiContext}, which uses the same units as - * {@link android.util.DisplayMetrics#density}. + * Returns the density of the area associated with this window or {@code UiContext}, + * which uses the same units as {@link android.util.DisplayMetrics#density}. * * @see android.util.DisplayMetrics#DENSITY_DEFAULT * @see android.util.DisplayMetrics#density diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index e6a8b7827b04..43bfe139c223 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -1562,9 +1562,8 @@ public class AccessibilityNodeInfo implements Parcelable { * describes the action. * </p> * <p> - * Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View, - * AccessibilityNodeInfoCompat.AccessibilityActionCompat)} to register an action directly on the - * view. + * Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View, CharSequence, + * AccessibilityViewCommand)} to register an action directly on the view. * <p> * <strong>Note:</strong> Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. @@ -5167,8 +5166,7 @@ public class AccessibilityNodeInfo implements Parcelable { * </p> * <aside class="note"> * <b>Note:</b> Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View, - * AccessibilityNodeInfoCompat.AccessibilityActionCompat)} to register an action directly on the - * view. + * CharSequence, AccessibilityViewCommand)} to register an action directly on the view. * </p> */ public static final class AccessibilityAction implements Parcelable { diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index a76780d18f71..32256b9b09c8 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -56,7 +56,7 @@ public class AnimationUtils { private static final int SEQUENTIALLY = 1; /** - * For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above, + * For apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and above, * this change ID enables to use expectedPresentationTime instead of the frameTime * for the frame start time . * diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index dc3d32317ded..bb815c0e8317 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -183,7 +183,8 @@ public abstract class ContentCaptureSession implements AutoCloseable { public static final int FLUSH_REASON_VIEW_TREE_APPEARED = 10; /** - * After {@link UPSIDE_DOWN_CAKE}, {@link #notifyViewsDisappeared(AutofillId, long[])} wraps + * After {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, + * {@link #notifyViewsDisappeared(AutofillId, long[])} wraps * the virtual children with a pair of view tree appearing and view tree appeared events. */ @ChangeId diff --git a/core/java/android/view/displayhash/DisplayHashResultCallback.java b/core/java/android/view/displayhash/DisplayHashResultCallback.java index 6e3d9a8786af..927874ffc3a2 100644 --- a/core/java/android/view/displayhash/DisplayHashResultCallback.java +++ b/core/java/android/view/displayhash/DisplayHashResultCallback.java @@ -106,7 +106,7 @@ public interface DisplayHashResultCallback { * {@link android.view.View#generateDisplayHash(String, Rect, Executor, * DisplayHashResultCallback)} results in an error and cannot generate a display hash. * - * @param errorCode One of the values in {@link DisplayHashErrorCode} + * @param errorCode the error code */ void onDisplayHashError(@DisplayHashErrorCode int errorCode); } diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java index a92420a2f373..c5114b9550db 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -47,7 +47,6 @@ import android.view.MotionEvent; import android.view.MotionEvent.ToolType; import android.view.View; import android.view.autofill.AutofillId; -import android.widget.Editor; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.InputMethodDebug; @@ -722,9 +721,9 @@ public class EditorInfo implements InputType, Parcelable { private boolean mIsStylusHandwritingEnabled; /** - * Set {@code true} if the {@link Editor} has + * Set {@code true} if the {@code Editor} has * {@link InputMethodManager#startStylusHandwriting stylus handwriting} enabled. - * {@code false} by default, {@link Editor} must set it {@code true} to indicate that + * {@code false} by default, {@code Editor} must set it {@code true} to indicate that * it supports stylus handwriting. * * @param enabled {@code true} if stylus handwriting is enabled. @@ -736,7 +735,7 @@ public class EditorInfo implements InputType, Parcelable { } /** - * Returns {@code true} when an {@link Editor} has stylus handwriting enabled. + * Returns {@code true} when an {@code Editor} has stylus handwriting enabled. * {@code false} by default. * @see #setStylusHandwritingEnabled(boolean) * @see InputMethodManager#isStylusHandwritingAvailable() diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 5bb1e9318175..8159af3ddd4a 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -100,7 +100,6 @@ import android.view.WindowManager; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; -import android.widget.Editor; import android.window.ImeOnBackInvokedDispatcher; import android.window.WindowOnBackInvokedDispatcher; @@ -2374,16 +2373,16 @@ public final class InputMethodManager { * Prepares delegation of starting stylus handwriting session to a different editor in same * or different window than the view on which initial handwriting stroke was detected. * - * Delegation can be used to start stylus handwriting session before the {@link Editor} view or + * Delegation can be used to start stylus handwriting session before the {@code Editor} view or * its {@link InputConnection} is started. Calling this method starts buffering of stylus * motion events until {@link #acceptStylusHandwritingDelegation(View)} is called, at which * point the handwriting session can be started and the buffered stylus motion events will be * delivered to the IME. * e.g. Delegation can be used when initial handwriting stroke is - * on a pseudo {@link Editor} like widget (with no {@link InputConnection}) but actual - * {@link Editor} is on a different window. + * on a pseudo {@code Editor} like widget (with no {@link InputConnection}) but actual + * {@code Editor} is on a different window. * - * <p> Note: If an actual {@link Editor} capable of {@link InputConnection} is being scribbled + * <p> Note: If an actual {@code Editor} capable of {@link InputConnection} is being scribbled * upon using stylus, use {@link #startStylusHandwriting(View)} instead.</p> * * @param delegatorView the view that receives initial stylus stroke and delegates it to the @@ -2402,21 +2401,21 @@ public final class InputMethodManager { * different window in a different package than the view on which initial handwriting stroke * was detected. * - * Delegation can be used to start stylus handwriting session before the {@link Editor} view or + * Delegation can be used to start stylus handwriting session before the {@code Editor} view or * its {@link InputConnection} is started. Calling this method starts buffering of stylus * motion events until {@link #acceptStylusHandwritingDelegation(View, String)} is called, at * which point the handwriting session can be started and the buffered stylus motion events will * be delivered to the IME. * e.g. Delegation can be used when initial handwriting stroke is - * on a pseudo {@link Editor} like widget (with no {@link InputConnection}) but actual - * {@link Editor} is on a different window in the given package. + * on a pseudo {@code Editor} like widget (with no {@link InputConnection}) but actual + * {@code Editor} is on a different window in the given package. * * <p>Note: If delegator and delegate are in same package use * {@link #prepareStylusHandwritingDelegation(View)} instead.</p> * * @param delegatorView the view that receives initial stylus stroke and delegates it to the * actual editor. Its window must {@link View#hasWindowFocus have focus}. - * @param delegatePackageName package name that contains actual {@link Editor} which should + * @param delegatePackageName package name that contains actual {@code Editor} which should * start stylus handwriting session by calling {@link #acceptStylusHandwritingDelegation}. * @see #prepareStylusHandwritingDelegation(View) * @see #acceptStylusHandwritingDelegation(View, String) diff --git a/core/java/android/view/inspector/PropertyReader.java b/core/java/android/view/inspector/PropertyReader.java index 5be0e3f3ccf2..78de7e16edd7 100644 --- a/core/java/android/view/inspector/PropertyReader.java +++ b/core/java/android/view/inspector/PropertyReader.java @@ -124,7 +124,7 @@ public interface PropertyReader { void readObject(int id, @Nullable Object value); /** - * Read a color packed into a {@link ColorInt} as a property. + * Read a color packed into an int as a property. * * @param id Identifier of the property from a {@link PropertyMapper} * @param value Value of the property diff --git a/core/java/android/window/SystemPerformanceHinter.java b/core/java/android/window/SystemPerformanceHinter.java index 07ac2922304e..2736b68845a2 100644 --- a/core/java/android/window/SystemPerformanceHinter.java +++ b/core/java/android/window/SystemPerformanceHinter.java @@ -211,7 +211,11 @@ public class SystemPerformanceHinter { session.displayId); mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl, FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); - mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_HIGH); + // smoothSwitchOnly is false to request a higher framerate, even if it means switching + // the display mode will cause would jank on non-VRR devices because keeping a lower + // refresh rate would mean a poorer user experience. + mTransaction.setFrameRateCategory( + displaySurfaceControl, FRAME_RATE_CATEGORY_HIGH, /* smoothSwitchOnly= */ false); transactionChanged = true; Trace.beginAsyncSection("PerfHint-framerate-" + session.displayId + "-" + session.reason, session.traceCookie); @@ -251,7 +255,11 @@ public class SystemPerformanceHinter { session.displayId); mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl, FRAME_RATE_SELECTION_STRATEGY_SELF); - mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_DEFAULT); + // smoothSwitchOnly is false to request a higher framerate, even if it means switching + // the display mode will cause would jank on non-VRR devices because keeping a lower + // refresh rate would mean a poorer user experience. + mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_DEFAULT, + /* smoothSwitchOnly= */ false); transactionChanged = true; Trace.endAsyncSection("PerfHint-framerate-" + session.displayId + "-" + session.reason, session.traceCookie); diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index d2a16a3a9212..61f340a856c4 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -29,6 +29,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; @@ -361,6 +362,15 @@ public final class TransitionInfo implements Parcelable { } /** + * Whether this transition contains any changes to the window hierarchy, + * including keyguard visibility. + */ + public boolean hasChangesOrSideEffects() { + return !mChanges.isEmpty() || isKeyguardGoingAway() + || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0; + } + + /** * Whether this transition includes keyguard going away. */ public boolean isKeyguardGoingAway() { diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index ba644eb0c03e..178c0d0d95be 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -963,10 +963,11 @@ static void nativeSetDefaultFrameRateCompatibility(JNIEnv* env, jclass clazz, jl } static void nativeSetFrameRateCategory(JNIEnv* env, jclass clazz, jlong transactionObj, - jlong nativeObject, jint category) { + jlong nativeObject, jint category, + jboolean smoothSwitchOnly) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); const auto ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); - transaction->setFrameRateCategory(ctrl, static_cast<int8_t>(category)); + transaction->setFrameRateCategory(ctrl, static_cast<int8_t>(category), smoothSwitchOnly); } static void nativeSetFrameRateSelectionStrategy(JNIEnv* env, jclass clazz, jlong transactionObj, @@ -2181,7 +2182,7 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetFrameRate }, {"nativeSetDefaultFrameRateCompatibility", "(JJI)V", (void*)nativeSetDefaultFrameRateCompatibility}, - {"nativeSetFrameRateCategory", "(JJI)V", + {"nativeSetFrameRateCategory", "(JJIZ)V", (void*)nativeSetFrameRateCategory}, {"nativeSetFrameRateSelectionStrategy", "(JJI)V", (void*)nativeSetFrameRateSelectionStrategy}, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ca768ad434f1..a54a563b1804 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -7235,13 +7235,23 @@ <!-- @SystemApi Required for the privileged assistant apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} - that receive voice trigger from the trusted hotword detection service. + that receive voice trigger from a trusted hotword detection service. <p>Protection level: signature|privileged|appop @FlaggedApi("android.permission.flags.voice_activation_permission_apis") @hide --> <permission android:name="android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO" android:protectionLevel="signature|privileged|appop" /> + <!-- @SystemApi Required for the privileged assistant apps targeting + {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} + that receive training data from the sandboxed hotword detection service or visual + query detection service. + <p>Protection level: internal|appop + @FlaggedApi("android.permission.flags.voice_activation_permission_apis") + @hide --> + <permission android:name="android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA" + android:protectionLevel="internal|appop" /> + <!-- @SystemApi Allows requesting the framework broadcast the {@link Intent#ACTION_DEVICE_CUSTOMIZATION_READY} intent. @hide --> diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java index 0941a2b6d263..6c14ee382bdc 100644 --- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java +++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java @@ -137,7 +137,7 @@ public class FontScaleConverterActivityTest { ); }); - PollingCheck.waitFor(/* timeout= */ 7000, () -> { + PollingCheck.waitFor(/* timeout= */ 10000, () -> { AtomicBoolean isActivityAtCorrectScale = new AtomicBoolean(false); rule.getScenario().onActivity(activity -> isActivityAtCorrectScale.set( @@ -163,7 +163,7 @@ public class FontScaleConverterActivityTest { }); PollingCheck.waitFor( - /* timeout= */ 5000, + /* timeout= */ 10000, () -> InstrumentationRegistry.getInstrumentation() .getContext() .getResources() diff --git a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java index 263e563bc224..6229530dc33f 100644 --- a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java +++ b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java @@ -31,6 +31,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -154,7 +155,8 @@ public class SystemPerformanceHinterTests { eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_CATEGORY_HIGH)); + eq(FRAME_RATE_CATEGORY_HIGH), + eq(false)); verify(mTransaction).applyAsyncUnsafe(); } @@ -171,7 +173,8 @@ public class SystemPerformanceHinterTests { eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_CATEGORY_DEFAULT)); + eq(FRAME_RATE_CATEGORY_DEFAULT), + eq(false)); verify(mTransaction).applyAsyncUnsafe(); } @@ -241,7 +244,8 @@ public class SystemPerformanceHinterTests { eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_CATEGORY_HIGH)); + eq(FRAME_RATE_CATEGORY_HIGH), + eq(false)); verify(mTransaction).setEarlyWakeupStart(); verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP)); @@ -261,7 +265,8 @@ public class SystemPerformanceHinterTests { eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_CATEGORY_DEFAULT)); + eq(FRAME_RATE_CATEGORY_DEFAULT), + eq(false)); verify(mTransaction).setEarlyWakeupEnd(); verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET)); @@ -281,7 +286,8 @@ public class SystemPerformanceHinterTests { eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_CATEGORY_DEFAULT)); + eq(FRAME_RATE_CATEGORY_DEFAULT), + eq(false)); verify(mTransaction).setEarlyWakeupEnd(); verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET)); @@ -299,7 +305,8 @@ public class SystemPerformanceHinterTests { eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_CATEGORY_HIGH)); + eq(FRAME_RATE_CATEGORY_HIGH), + eq(false)); verify(mTransaction).setEarlyWakeupStart(); verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP)); @@ -310,7 +317,7 @@ public class SystemPerformanceHinterTests { mHinter.startSession(HINT_ALL, DEFAULT_DISPLAY_ID, TEST_OTHER_REASON); // Verify we never call SF and perf manager since session1 is already running verify(mTransaction, never()).setFrameRateSelectionStrategy(any(), anyInt()); - verify(mTransaction, never()).setFrameRateCategory(any(), anyInt()); + verify(mTransaction, never()).setFrameRateCategory(any(), anyInt(), anyBoolean()); verify(mTransaction, never()).setEarlyWakeupEnd(); verify(mTransaction, never()).applyAsyncUnsafe(); verify(mAdpfSession, never()).sendHint(anyInt()); @@ -318,7 +325,7 @@ public class SystemPerformanceHinterTests { session2.close(); // Verify we have not cleaned up because session1 is still running verify(mTransaction, never()).setFrameRateSelectionStrategy(any(), anyInt()); - verify(mTransaction, never()).setFrameRateCategory(any(), anyInt()); + verify(mTransaction, never()).setFrameRateCategory(any(), anyInt(), anyBoolean()); verify(mTransaction, never()).setEarlyWakeupEnd(); verify(mTransaction, never()).applyAsyncUnsafe(); verify(mAdpfSession, never()).sendHint(anyInt()); @@ -330,7 +337,8 @@ public class SystemPerformanceHinterTests { eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_CATEGORY_DEFAULT)); + eq(FRAME_RATE_CATEGORY_DEFAULT), + eq(false)); verify(mTransaction).setEarlyWakeupEnd(); verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET)); @@ -348,7 +356,8 @@ public class SystemPerformanceHinterTests { eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_CATEGORY_HIGH)); + eq(FRAME_RATE_CATEGORY_HIGH), + eq(false)); verify(mTransaction).setEarlyWakeupStart(); verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP)); @@ -363,7 +372,8 @@ public class SystemPerformanceHinterTests { eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN)); verify(mTransaction).setFrameRateCategory( eq(mSecondaryDisplayRoot), - eq(FRAME_RATE_CATEGORY_HIGH)); + eq(FRAME_RATE_CATEGORY_HIGH), + eq(false)); verify(mTransaction, never()).setEarlyWakeupStart(); verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession, never()).sendHint(anyInt()); @@ -378,13 +388,15 @@ public class SystemPerformanceHinterTests { eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_CATEGORY_DEFAULT)); + eq(FRAME_RATE_CATEGORY_DEFAULT), + eq(false)); verify(mTransaction, never()).setFrameRateSelectionStrategy( eq(mSecondaryDisplayRoot), anyInt()); verify(mTransaction, never()).setFrameRateCategory( eq(mSecondaryDisplayRoot), - anyInt()); + anyInt(), + eq(false)); verify(mTransaction, never()).setEarlyWakeupEnd(); verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession, never()).sendHint(anyInt()); @@ -401,7 +413,8 @@ public class SystemPerformanceHinterTests { eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); verify(mTransaction).setFrameRateCategory( eq(mSecondaryDisplayRoot), - eq(FRAME_RATE_CATEGORY_DEFAULT)); + eq(FRAME_RATE_CATEGORY_DEFAULT), + eq(false)); verify(mTransaction).setEarlyWakeupEnd(); verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET)); diff --git a/graphics/java/Android.bp b/graphics/java/Android.bp index 63d1f6d6f2d6..db37a38756d2 100644 --- a/graphics/java/Android.bp +++ b/graphics/java/Android.bp @@ -7,6 +7,12 @@ package { default_applicable_licenses: ["frameworks_base_license"], } +aconfig_declarations { + name: "framework_graphics_flags", + package: "com.android.graphics.flags", + srcs: ["android/framework_graphics.aconfig"], +} + filegroup { name: "framework-graphics-nonupdatable-sources", srcs: [ diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig new file mode 100644 index 000000000000..e030dad6bf14 --- /dev/null +++ b/graphics/java/android/framework_graphics.aconfig @@ -0,0 +1,8 @@ +package: "com.android.graphics.flags" + +flag { + name: "exact_compute_bounds" + namespace: "framework_graphics" + description: "Add a function without unused exact param for computeBounds." + bug: "304478551" +}
\ No newline at end of file diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java index b5fb13db4ae4..0a6fb8424094 100644 --- a/graphics/java/android/graphics/Gainmap.java +++ b/graphics/java/android/graphics/Gainmap.java @@ -16,11 +16,14 @@ package android.graphics; +import android.annotation.FlaggedApi; import android.annotation.FloatRange; import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import com.android.graphics.hwui.flags.Flags; + import libcore.util.NativeAllocationRegistry; /** @@ -125,6 +128,7 @@ public final class Gainmap implements Parcelable { * Creates a new gainmap using the provided gainmap as the metadata source and the provided * bitmap as the replacement for the gainmapContents */ + @FlaggedApi(Flags.FLAG_GAINMAP_CONSTRUCTOR_WITH_METADATA) public Gainmap(@NonNull Gainmap gainmap, @NonNull Bitmap gainmapContents) { this(gainmapContents, nCreateCopy(gainmap.mNativePtr)); } diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java index c9c1b23d874c..deb89faf3419 100644 --- a/graphics/java/android/graphics/Path.java +++ b/graphics/java/android/graphics/Path.java @@ -16,11 +16,14 @@ package android.graphics; +import android.annotation.FlaggedApi; import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; +import com.android.graphics.flags.Flags; + import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; @@ -309,6 +312,7 @@ public class Path { * * @param bounds Returns the computed bounds of the path's control points. */ + @FlaggedApi(Flags.FLAG_EXACT_COMPUTE_BOUNDS) public void computeBounds(@NonNull RectF bounds) { nComputeBounds(mNativePath, bounds); } diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java index 4461f39fd006..c2a7a84ad809 100644 --- a/graphics/java/android/graphics/drawable/RippleShader.java +++ b/graphics/java/android/graphics/drawable/RippleShader.java @@ -109,9 +109,8 @@ final class RippleShader extends RuntimeShader { + " float alpha = min(fadeIn, 1. - fadeOutNoise);\n" + " vec2 uv = p * in_resolutionScale;\n" + " vec2 densityUv = uv - mod(uv, in_noiseScale);\n" - + " float turbulence = turbulence(uv, in_turbulencePhase);\n" - + " float sparkleAlpha = sparkles(densityUv, in_noisePhase) * ring * alpha " - + "* turbulence;\n" + + " float turb = turbulence(uv, in_turbulencePhase);\n" + + " float sparkleAlpha = sparkles(densityUv, in_noisePhase) * ring * alpha * turb;\n" + " float fade = min(fadeIn, 1. - fadeOutRipple);\n" + " float waveAlpha = softCircle(p, center, in_maxRadius * scaleIn, 1.) * fade " + "* in_color.a;\n" diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java index 87c438a5b37d..ba0ef20c412e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java @@ -19,13 +19,12 @@ package com.android.wm.shell.transition; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.IBinder; +import android.util.Slog; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; -import java.util.ArrayList; - /** * A Simple handler that tracks SLEEP transitions. We track them specially since we (ab)use these * as sentinels for fast-forwarding through animations when the screen is off. @@ -34,30 +33,25 @@ import java.util.ArrayList; * don't register it like a normal handler. */ class SleepHandler implements Transitions.TransitionHandler { - final ArrayList<IBinder> mSleepTransitions = new ArrayList<>(); - @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - mSleepTransitions.remove(transition); - startTransaction.apply(); - finishCallback.onTransitionFinished(null); - return true; + if (info.hasChangesOrSideEffects()) { + Slog.e(Transitions.TAG, "Real changes included in a SLEEP transition"); + return false; + } else { + startTransaction.apply(); + finishCallback.onTransitionFinished(null); + return true; + } } @Override @Nullable public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request) { - mSleepTransitions.add(transition); return new WindowContainerTransaction(); } - - @Override - public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, - @Nullable SurfaceControl.Transaction finishTransaction) { - mSleepTransitions.remove(transition); - } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index b9c9049e08c4..da83d4c0a122 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -1169,7 +1169,7 @@ public class ShellTransitionTests extends ShellTestCase { } @Test - public void testEmptyTransitionStillReportsKeyguardGoingAway() { + public void testEmptyTransition_withKeyguardGoingAway_plays() { Transitions transitions = createTestTransitions(); transitions.replaceDefaultHandlerForTest(mDefaultHandler); @@ -1188,6 +1188,65 @@ public class ShellTransitionTests extends ShellTestCase { } @Test + public void testSleepTransition_withKeyguardGoingAway_plays(){ + Transitions transitions = createTestTransitions(); + transitions.replaceDefaultHandlerForTest(mDefaultHandler); + + IBinder transitToken = new Binder(); + transitions.requestStartTransition(transitToken, + new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */)); + + // Make a no-op transition + TransitionInfo info = new TransitionInfoBuilder( + TRANSIT_SLEEP, TRANSIT_FLAG_KEYGUARD_GOING_AWAY, true /* noOp */).build(); + transitions.onTransitionReady(transitToken, info, new StubTransaction(), + new StubTransaction()); + + // If keyguard-going-away flag set, then it shouldn't be aborted. + assertEquals(1, mDefaultHandler.activeCount()); + } + + @Test + public void testSleepTransition_withChanges_plays(){ + Transitions transitions = createTestTransitions(); + transitions.replaceDefaultHandlerForTest(mDefaultHandler); + + IBinder transitToken = new Binder(); + transitions.requestStartTransition(transitToken, + new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */)); + + // Make a transition with some changes + TransitionInfo info = new TransitionInfoBuilder(TRANSIT_SLEEP) + .addChange(TRANSIT_OPEN).build(); + info.setTrack(0); + transitions.onTransitionReady(transitToken, info, new StubTransaction(), + new StubTransaction()); + + // If there is an actual change, then it shouldn't be aborted. + assertEquals(1, mDefaultHandler.activeCount()); + } + + + @Test + public void testSleepTransition_empty_SyncBySleepHandler() { + Transitions transitions = createTestTransitions(); + transitions.replaceDefaultHandlerForTest(mDefaultHandler); + + IBinder transitToken = new Binder(); + transitions.requestStartTransition(transitToken, + new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */)); + + // Make a no-op transition + TransitionInfo info = new TransitionInfoBuilder( + TRANSIT_SLEEP, 0x0, true /* noOp */).build(); + transitions.onTransitionReady(transitToken, info, new StubTransaction(), + new StubTransaction()); + + // If there is nothing to actually play, it should not be offered to handlers. + assertEquals(0, mDefaultHandler.activeCount()); + } + + @Test public void testMultipleTracks() { Transitions transitions = createTestTransitions(); transitions.replaceDefaultHandlerForTest(mDefaultHandler); diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig index d0d3c5e7ece1..e672b983d509 100644 --- a/libs/hwui/aconfig/hwui_flags.aconfig +++ b/libs/hwui/aconfig/hwui_flags.aconfig @@ -20,3 +20,10 @@ flag { description: "APIs to help enable animations involving gainmaps" bug: "296482289" } + +flag { + name: "gainmap_constructor_with_metadata" + namespace: "core_graphics" + description: "APIs to create a new gainmap with a bitmap for metadata." + bug: "304478551" +} diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 90850d36b612..22c586248705 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -26,6 +26,7 @@ #include <gui/TraceUtils.h> #include <include/gpu/ganesh/SkSurfaceGanesh.h> #include <include/gpu/ganesh/vk/GrVkBackendSurface.h> +#include <include/gpu/ganesh/vk/GrVkDirectContext.h> #include <ui/FatVector.h> #include <vk/GrVkExtensions.h> #include <vk/GrVkTypes.h> @@ -435,7 +436,7 @@ sk_sp<GrDirectContext> VulkanManager::createContext(GrContextOptions& options, options.fContextDeleteContext = this; options.fContextDeleteProc = onGrContextReleased; - return GrDirectContext::MakeVulkan(backendContext, options); + return GrDirectContexts::MakeVulkan(backendContext, options); } VkFunctorInitParams VulkanManager::getVkFunctorInitParams() const { diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java index ceb3858eb0b3..b002bbf20c08 100644 --- a/media/java/android/media/AudioFormat.java +++ b/media/java/android/media/AudioFormat.java @@ -1282,10 +1282,8 @@ public final class AudioFormat implements Parcelable { * {@link AudioFormat#CHANNEL_OUT_BACK_CENTER}, * {@link AudioFormat#CHANNEL_OUT_SIDE_LEFT}, * {@link AudioFormat#CHANNEL_OUT_SIDE_RIGHT}. - * <p> For a valid {@link AudioTrack} channel position mask, - * the following conditions apply: - * <br> (1) at most eight channel positions may be used; - * <br> (2) right/left pairs should be matched. + * <p> For output or {@link AudioTrack}, channel position masks which do not contain + * matched left/right pairs are invalid. * <p> For input or {@link AudioRecord}, the mask should be * {@link AudioFormat#CHANNEL_IN_MONO} or * {@link AudioFormat#CHANNEL_IN_STEREO}. {@link AudioFormat#CHANNEL_IN_MONO} is diff --git a/omapi/aidl/Android.bp b/omapi/aidl/Android.bp index 58bcd1d49c69..e71597a27a39 100644 --- a/omapi/aidl/Android.bp +++ b/omapi/aidl/Android.bp @@ -24,6 +24,11 @@ aidl_interface { backend: { java: { sdk_version: "module_current", + apex_available: [ + "//apex_available:platform", + "com.android.nfcservices", + ], + }, rust: { enabled: true, diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 4f55e8b3bd4e..4954cb482120 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -165,7 +165,6 @@ android_library { "SystemUISharedLib", "SystemUI-statsd", "SettingsLib", - "com_android_systemui_communal_flags_lib", "com_android_systemui_flags_lib", "androidx.core_core-ktx", "androidx.viewpager2_viewpager2", diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp index b18c7900a168..dc4208e87207 100644 --- a/packages/SystemUI/aconfig/Android.bp +++ b/packages/SystemUI/aconfig/Android.bp @@ -2,8 +2,7 @@ aconfig_declarations { name: "com_android_systemui_flags", package: "com.android.systemui", srcs: [ - "systemui.aconfig", - "accessibility.aconfig", + "*.aconfig", ], } @@ -11,16 +10,3 @@ java_aconfig_library { name: "com_android_systemui_flags_lib", aconfig_declarations: "com_android_systemui_flags", } - -aconfig_declarations { - name: "com_android_systemui_communal_flags", - package: "com.android.systemui.communal", - srcs: [ - "communal.aconfig", - ], -} - -java_aconfig_library { - name: "com_android_systemui_communal_flags_lib", - aconfig_declarations: "com_android_systemui_communal_flags", -} diff --git a/packages/SystemUI/aconfig/communal.aconfig b/packages/SystemUI/aconfig/communal.aconfig index 8ecb9842ddc5..2c6ff979cc7f 100644 --- a/packages/SystemUI/aconfig/communal.aconfig +++ b/packages/SystemUI/aconfig/communal.aconfig @@ -1,4 +1,4 @@ -package: "com.android.systemui.communal" +package: "com.android.systemui" flag { name: "communal_hub" diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt index 2f10ad3e6486..b9bafd4926fa 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt @@ -89,15 +89,15 @@ class MediaProjectionPermissionDialog( } return listOf( ScreenShareOption( - mode = ENTIRE_SCREEN, - spinnerText = R.string.screen_share_permission_dialog_option_entire_screen, - warningText = entireScreenWarningText - ), - ScreenShareOption( mode = SINGLE_APP, spinnerText = R.string.screen_share_permission_dialog_option_single_app, warningText = singleAppWarningText, spinnerDisabledText = singleAppDisabledText, + ), + ScreenShareOption( + mode = ENTIRE_SCREEN, + spinnerText = R.string.screen_share_permission_dialog_option_entire_screen, + warningText = entireScreenWarningText ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt index 37e8d9f26ee3..9bd57832c4df 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt @@ -23,8 +23,8 @@ import kotlin.annotation.Retention @IntDef(ENTIRE_SCREEN, SINGLE_APP) annotation class ScreenShareMode -const val ENTIRE_SCREEN = 0 -const val SINGLE_APP = 1 +const val SINGLE_APP = 0 +const val ENTIRE_SCREEN = 1 class ScreenShareOption( @ScreenShareMode val mode: Int, diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt index a1d5d98ba9e9..ade93b1a913e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt @@ -186,14 +186,14 @@ class ScreenRecordPermissionDialog( private fun createOptionList(): List<ScreenShareOption> { return listOf( ScreenShareOption( - ENTIRE_SCREEN, - R.string.screen_share_permission_dialog_option_entire_screen, - R.string.screenrecord_permission_dialog_warning_entire_screen - ), - ScreenShareOption( SINGLE_APP, R.string.screen_share_permission_dialog_option_single_app, R.string.screenrecord_permission_dialog_warning_single_app + ), + ScreenShareOption( + ENTIRE_SCREEN, + R.string.screen_share_permission_dialog_option_entire_screen, + R.string.screenrecord_permission_dialog_warning_entire_screen ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt index 0554c5855d61..1a28ddc9719b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt @@ -72,10 +72,6 @@ class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents { listener.onShadeExpansionFullyChanged(qsExpanded) } - fun removeFullExpansionListener(listener: ShadeFullExpansionListener) { - fullExpansionListeners.remove(listener) - } - fun addQsExpansionListener(listener: ShadeQsExpansionListener) { qsExpansionListeners.add(listener) listener.onQsExpansionChanged(qsExpanded) @@ -99,11 +95,6 @@ class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents { stateListeners.add(listener) } - /** Removes a state listener. */ - fun removeStateListener(listener: ShadeStateListener) { - stateListeners.remove(listener) - } - override fun addShadeStateEventsListener(listener: ShadeStateEventsListener) { shadeStateEventsListeners.addIfAbsent(listener) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index e487a6fb9617..99370e963c9f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt @@ -34,7 +34,7 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.Share import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository -import com.android.systemui.user.domain.interactor.UserInteractor +import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import javax.inject.Provider @@ -71,7 +71,7 @@ constructor( keyguardTransitionInteractor: KeyguardTransitionInteractor, powerInteractor: PowerInteractor, userSetupRepository: UserSetupRepository, - userInteractor: UserInteractor, + userRepository: UserRepository, sharedNotificationContainerInteractor: SharedNotificationContainerInteractor, repository: ShadeRepository, ) { @@ -156,12 +156,13 @@ constructor( * * TODO(b/300258424) remove all but the first sentence of this comment */ - val isAnyExpanded: Flow<Boolean> = + val isAnyExpanded: StateFlow<Boolean> = if (sceneContainerFlags.isEnabled()) { - anyExpansion.map { it > 0f }.distinctUntilChanged() - } else { - repository.legacyExpandedOrAwaitingInputTransfer - } + anyExpansion.map { it > 0f }.distinctUntilChanged() + } else { + repository.legacyExpandedOrAwaitingInputTransfer + } + .stateIn(scope, SharingStarted.Eagerly, false) /** * Whether the user is expanding or collapsing the shade with user input. This will be true even @@ -227,7 +228,7 @@ constructor( isDeviceProvisioned && // Disallow QS during setup if it's a simple user switcher. (The user intends to // use the lock screen user switcher, QS is not needed.) - (isUserSetup || !userInteractor.isSimpleUserSwitcher) && + (isUserSetup || !userRepository.isSimpleUserSwitcher()) && isShadeEnabled && disableFlags.isQuickSettingsEnabled() && !isDozing diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 2147510bbde5..f32f1c2dcd25 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -16,9 +16,16 @@ package com.android.systemui.statusbar; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; +import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS; +import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS; +import static android.os.UserHandle.USER_NULL; +import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS; +import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS; import static com.android.systemui.DejankUtils.whitelistIpcs; +import android.annotation.SuppressLint; +import android.annotation.UserIdInt; import android.app.ActivityOptions; import android.app.KeyguardManager; import android.app.Notification; @@ -30,8 +37,10 @@ import android.content.IntentFilter; import android.content.IntentSender; import android.content.pm.UserInfo; import android.database.ContentObserver; +import android.net.Uri; import android.os.Handler; import android.os.HandlerExecutor; +import android.os.Looper; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; @@ -41,14 +50,18 @@ import android.util.SparseBooleanArray; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; +import androidx.annotation.WorkerThread; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.Dumpable; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlagsClassic; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.recents.OverviewProxyService; @@ -63,7 +76,9 @@ import com.android.systemui.util.settings.SecureSettings; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -84,6 +99,11 @@ public class NotificationLockscreenUserManagerImpl implements private final SecureSettings mSecureSettings; private final Object mLock = new Object(); + private static final Uri SHOW_LOCKSCREEN = + Settings.Secure.getUriFor(LOCK_SCREEN_SHOW_NOTIFICATIONS); + private static final Uri SHOW_PRIVATE_LOCKSCREEN = + Settings.Secure.getUriFor(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + private final Lazy<NotificationVisibilityProvider> mVisibilityProviderLazy; private final Lazy<CommonNotifCollection> mCommonNotifCollectionLazy; private final DevicePolicyManager mDevicePolicyManager; @@ -91,6 +111,23 @@ public class NotificationLockscreenUserManagerImpl implements private final SparseBooleanArray mUsersWithSeparateWorkChallenge = new SparseBooleanArray(); private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray(); private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray(); + + // The variables between mUsersDpcAllowingNotifications and + // mUsersUsersAllowingPrivateNotifications (inclusive) are written on a background thread + // and read on the main thread. Because the pipeline needs these values, adding locks would + // introduce too much jank. This means that some pipeline runs could get incorrect values, that + // would be fixed on the next pipeline run. We think this will be rare since a pipeline run + // would have to overlap with a DPM sync or a user changing a value in Settings, and we run the + // pipeline frequently enough that it should be corrected by the next time it matters for the + // user. + private final SparseBooleanArray mUsersDpcAllowingNotifications = new SparseBooleanArray(); + private final SparseBooleanArray mUsersUsersAllowingNotifications = new SparseBooleanArray(); + private boolean mKeyguardAllowingNotifications = true; + private final SparseBooleanArray mUsersDpcAllowingPrivateNotifications + = new SparseBooleanArray(); + private final SparseBooleanArray mUsersUsersAllowingPrivateNotifications + = new SparseBooleanArray(); + private final SparseBooleanArray mUsersInLockdownLatestResult = new SparseBooleanArray(); private final SparseBooleanArray mShouldHideNotifsLatestResult = new SparseBooleanArray(); private final UserManager mUserManager; @@ -99,24 +136,39 @@ public class NotificationLockscreenUserManagerImpl implements private final BroadcastDispatcher mBroadcastDispatcher; private final NotificationClickNotifier mClickNotifier; private final Lazy<OverviewProxyService> mOverviewProxyServiceLazy; + private final FeatureFlagsClassic mFeatureFlags; private boolean mShowLockscreenNotifications; private LockPatternUtils mLockPatternUtils; protected KeyguardManager mKeyguardManager; private int mState = StatusBarState.SHADE; private final ListenerSet<NotificationStateChangedListener> mNotifStateChangedListeners = new ListenerSet<>(); + private final Collection<Uri> mLockScreenUris = new ArrayList<>(); + protected final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); - if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) && - isCurrentProfile(getSendingUserId())) { - mUsersAllowingPrivateNotifications.clear(); - updateLockscreenNotificationSetting(); - // TODO(b/231976036): Consolidate pipeline invalidations related to this event - // notifyNotificationStateChanged(); + if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action)) { + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + boolean changed = updateDpcSettings(getSendingUserId()); + if (mCurrentUserId == getSendingUserId()) { + changed |= updateLockscreenNotificationSetting(); + } + if (changed) { + notifyNotificationStateChanged(); + } + } else { + if (isCurrentProfile(getSendingUserId())) { + mUsersAllowingPrivateNotifications.clear(); + updateLockscreenNotificationSetting(); + // TODO(b/231976036): Consolidate pipeline invalidations related to this + // event + // notifyNotificationStateChanged(); + } + } } } }; @@ -136,6 +188,14 @@ public class NotificationLockscreenUserManagerImpl implements updateCurrentProfilesCache(); break; case Intent.ACTION_USER_ADDED: + updateCurrentProfilesCache(); + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); + mBackgroundHandler.post(() -> { + initValuesForUser(userId); + }); + } + break; case Intent.ACTION_MANAGED_PROFILE_AVAILABLE: case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE: updateCurrentProfilesCache(); @@ -193,6 +253,8 @@ public class NotificationLockscreenUserManagerImpl implements protected final Context mContext; private final Handler mMainHandler; + private final Handler mBackgroundHandler; + private final Executor mBackgroundExecutor; protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>(); protected final SparseArray<UserInfo> mCurrentManagedProfiles = new SparseArray<>(); @@ -214,13 +276,18 @@ public class NotificationLockscreenUserManagerImpl implements KeyguardManager keyguardManager, StatusBarStateController statusBarStateController, @Main Handler mainHandler, + @Background Handler backgroundHandler, + @Background Executor backgroundExecutor, DeviceProvisionedController deviceProvisionedController, KeyguardStateController keyguardStateController, SecureSettings secureSettings, DumpManager dumpManager, - LockPatternUtils lockPatternUtils) { + LockPatternUtils lockPatternUtils, + FeatureFlagsClassic featureFlags) { mContext = context; mMainHandler = mainHandler; + mBackgroundHandler = backgroundHandler; + mBackgroundExecutor = backgroundExecutor; mDevicePolicyManager = devicePolicyManager; mUserManager = userManager; mUserTracker = userTracker; @@ -236,6 +303,10 @@ public class NotificationLockscreenUserManagerImpl implements mDeviceProvisionedController = deviceProvisionedController; mSecureSettings = secureSettings; mKeyguardStateController = keyguardStateController; + mFeatureFlags = featureFlags; + + mLockScreenUris.add(SHOW_LOCKSCREEN); + mLockScreenUris.add(SHOW_PRIVATE_LOCKSCREEN); dumpManager.registerDumpable(this); } @@ -243,16 +314,53 @@ public class NotificationLockscreenUserManagerImpl implements public void setUpWithPresenter(NotificationPresenter presenter) { mPresenter = presenter; - mLockscreenSettingsObserver = new ContentObserver(mMainHandler) { + mLockscreenSettingsObserver = new ContentObserver( + mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD) + ? mBackgroundHandler + : mMainHandler) { + @Override - public void onChange(boolean selfChange) { - // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or - // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ... - mUsersAllowingPrivateNotifications.clear(); - mUsersAllowingNotifications.clear(); - // ... and refresh all the notifications - updateLockscreenNotificationSetting(); - notifyNotificationStateChanged(); + public void onChange(boolean selfChange, Collection<Uri> uris, int flags) { + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + @SuppressLint("MissingPermission") + List<UserInfo> users = mUserManager.getUsers(); + for (int i = users.size() - 1; i >= 0; i--) { + onChange(selfChange, uris, flags,users.get(i).getUserHandle()); + } + } else { + // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or + // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ... + mUsersAllowingPrivateNotifications.clear(); + mUsersAllowingNotifications.clear(); + // ... and refresh all the notifications + updateLockscreenNotificationSetting(); + notifyNotificationStateChanged(); + } + } + + // Note: even though this is an override, this method is not called by the OS + // since we're not in system_server. We are using it internally for cases when + // we have a single user id available (e.g. from USER_ADDED). + @Override + public void onChange(boolean selfChange, Collection<Uri> uris, + int flags, UserHandle user) { + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + boolean changed = false; + for (Uri uri: uris) { + if (SHOW_LOCKSCREEN.equals(uri)) { + changed |= updateUserShowSettings(user.getIdentifier()); + } else if (SHOW_PRIVATE_LOCKSCREEN.equals(uri)) { + changed |= updateUserShowPrivateSettings(user.getIdentifier()); + } + } + + if (mCurrentUserId == user.getIdentifier()) { + changed |= updateLockscreenNotificationSetting(); + } + if (changed) { + notifyNotificationStateChanged(); + } + } } }; @@ -268,23 +376,26 @@ public class NotificationLockscreenUserManagerImpl implements }; mContext.getContentResolver().registerContentObserver( - mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false, + SHOW_LOCKSCREEN, false, mLockscreenSettingsObserver, UserHandle.USER_ALL); mContext.getContentResolver().registerContentObserver( - mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), + SHOW_PRIVATE_LOCKSCREEN, true, mLockscreenSettingsObserver, UserHandle.USER_ALL); - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false, - mSettingsObserver); + if (!mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false, + mSettingsObserver); + } mBroadcastDispatcher.registerReceiver(mAllUsersReceiver, new IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), - null /* handler */, UserHandle.ALL); + mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD) + ? mBackgroundExecutor : null, UserHandle.ALL); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_ADDED); @@ -305,7 +416,23 @@ public class NotificationLockscreenUserManagerImpl implements mCurrentUserId = mUserTracker.getUserId(); // in case we reg'd receiver too late updateCurrentProfilesCache(); - mSettingsObserver.onChange(false); // set up + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + // Set up + mBackgroundHandler.post(() -> { + @SuppressLint("MissingPermission") List<UserInfo> users = mUserManager.getUsers(); + for (int i = users.size() - 1; i >= 0; i--) { + initValuesForUser(users.get(i).id); + } + }); + } else { + mSettingsObserver.onChange(false); // set up + } + } + + private void initValuesForUser(@UserIdInt int userId) { + mLockscreenSettingsObserver.onChange( + false, mLockScreenUris, 0, UserHandle.of(userId)); + updateDpcSettings(userId); } public boolean shouldShowLockscreenNotifications() { @@ -322,17 +449,75 @@ public class NotificationLockscreenUserManagerImpl implements mShowLockscreenNotifications = show; } - protected void updateLockscreenNotificationSetting() { - final boolean show = mSecureSettings.getIntForUser( - Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, - 1, - mCurrentUserId) != 0; - final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures( - null /* admin */, mCurrentUserId); - final boolean allowedByDpm = (dpmFlags - & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; + protected boolean updateLockscreenNotificationSetting() { + boolean show; + boolean allowedByDpm; + + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + show = mUsersUsersAllowingNotifications.get(mCurrentUserId) + && mKeyguardAllowingNotifications; + // If DPC never notified us about a user, that means they have no policy for the user, + // and they allow the behavior + allowedByDpm = mUsersDpcAllowingNotifications.get(mCurrentUserId, true); + } else { + show = mSecureSettings.getIntForUser( + LOCK_SCREEN_SHOW_NOTIFICATIONS, + 1, + mCurrentUserId) != 0; + final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures( + null /* admin */, mCurrentUserId); + allowedByDpm = (dpmFlags + & KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; + } + final boolean oldValue = mShowLockscreenNotifications; setShowLockscreenNotifications(show && allowedByDpm); + + return oldValue != mShowLockscreenNotifications; + } + + @WorkerThread + protected boolean updateDpcSettings(int userId) { + boolean originalAllowLockscreen = mUsersDpcAllowingNotifications.get(userId); + boolean originalAllowPrivate = mUsersDpcAllowingPrivateNotifications.get(userId); + final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures( + null /* admin */, userId); + final boolean allowedLockscreen = (dpmFlags & KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; + final boolean allowedPrivate = (dpmFlags & KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0; + mUsersDpcAllowingNotifications.put(userId, allowedLockscreen); + mUsersDpcAllowingPrivateNotifications.put(userId, allowedPrivate); + return (originalAllowLockscreen != allowedLockscreen) + || (originalAllowPrivate != allowedPrivate); + } + + @WorkerThread + private boolean updateUserShowSettings(int userId) { + boolean originalAllowLockscreen = mUsersUsersAllowingNotifications.get(userId); + boolean newAllowLockscreen = mSecureSettings.getIntForUser( + LOCK_SCREEN_SHOW_NOTIFICATIONS, + 1, + userId) != 0; + mUsersUsersAllowingNotifications.put(userId, newAllowLockscreen); + boolean keyguardChanged = updateGlobalKeyguardSettings(); + return (newAllowLockscreen != originalAllowLockscreen) || keyguardChanged; + } + + @WorkerThread + private boolean updateUserShowPrivateSettings(int userId) { + boolean originalValue = mUsersUsersAllowingPrivateNotifications.get(userId); + boolean newValue = mSecureSettings.getIntForUser( + LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, + 0, + userId) != 0; + mUsersUsersAllowingPrivateNotifications.put(userId, newValue); + return (newValue != originalValue); + } + + @WorkerThread + private boolean updateGlobalKeyguardSettings() { + final boolean oldValue = mKeyguardAllowingNotifications; + mKeyguardAllowingNotifications = mKeyguardManager.getPrivateNotificationsAllowed(); + return oldValue != mKeyguardAllowingNotifications; } /** @@ -340,21 +525,41 @@ public class NotificationLockscreenUserManagerImpl implements * when the lockscreen is in "public" (secure & locked) mode? */ public boolean userAllowsPrivateNotificationsInPublic(int userHandle) { - if (userHandle == UserHandle.USER_ALL) { - return true; - } + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + if (userHandle == UserHandle.USER_ALL) { + userHandle = mCurrentUserId; + } + if (mUsersUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { + // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe + // default value before moving to 'released' + Log.wtf(TAG, "Asking for redact notifs setting too early", new Throwable()); + updateUserShowPrivateSettings(userHandle); + } + if (mUsersDpcAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { + // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe + // default value before moving to 'released' + Log.wtf(TAG, "Asking for redact notifs dpm override too early", new Throwable()); + updateDpcSettings(userHandle); + } + return mUsersUsersAllowingPrivateNotifications.get(userHandle) + && mUsersDpcAllowingPrivateNotifications.get(userHandle); + } else { + if (userHandle == UserHandle.USER_ALL) { + return true; + } - if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { - final boolean allowedByUser = 0 != mSecureSettings.getIntForUser( - Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle); - final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle, - DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); - final boolean allowed = allowedByUser && allowedByDpm; - mUsersAllowingPrivateNotifications.append(userHandle, allowed); - return allowed; - } + if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { + final boolean allowedByUser = 0 != mSecureSettings.getIntForUser( + LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle); + final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle, + KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + final boolean allowed = allowedByUser && allowedByDpm; + mUsersAllowingPrivateNotifications.append(userHandle, allowed); + return allowed; + } - return mUsersAllowingPrivateNotifications.get(userHandle); + return mUsersAllowingPrivateNotifications.get(userHandle); + } } /** @@ -406,21 +611,44 @@ public class NotificationLockscreenUserManagerImpl implements * "public" (secure & locked) mode? */ public boolean userAllowsNotificationsInPublic(int userHandle) { - if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) { - return true; - } + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + // Unlike 'show private', settings does not show a copy of this setting for each + // profile, so it inherits from the parent user. + if (userHandle == UserHandle.USER_ALL || mCurrentManagedProfiles.contains(userHandle)) { + userHandle = mCurrentUserId; + } + if (mUsersUsersAllowingNotifications.indexOfKey(userHandle) < 0) { + // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe + // default value before moving to 'released' + Log.wtf(TAG, "Asking for show notifs setting too early", new Throwable()); + updateUserShowSettings(userHandle); + } + if (mUsersDpcAllowingNotifications.indexOfKey(userHandle) < 0) { + // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe + // default value before moving to 'released' + Log.wtf(TAG, "Asking for show notifs dpm override too early", new Throwable()); + updateDpcSettings(userHandle); + } + return mUsersUsersAllowingNotifications.get(userHandle) + && mUsersDpcAllowingNotifications.get(userHandle) + && mKeyguardAllowingNotifications; + } else { + if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) { + return true; + } - if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) { - final boolean allowedByUser = 0 != mSecureSettings.getIntForUser( - Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle); - final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle, - DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); - final boolean allowedBySystem = mKeyguardManager.getPrivateNotificationsAllowed(); - final boolean allowed = allowedByUser && allowedByDpm && allowedBySystem; - mUsersAllowingNotifications.append(userHandle, allowed); - return allowed; + if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) { + final boolean allowedByUser = 0 != mSecureSettings.getIntForUser( + LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle); + final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle, + KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); + final boolean allowedBySystem = mKeyguardManager.getPrivateNotificationsAllowed(); + final boolean allowed = allowedByUser && allowedByDpm && allowedBySystem; + mUsersAllowingNotifications.append(userHandle, allowed); + return allowed; + } + return mUsersAllowingNotifications.get(userHandle); } - return mUsersAllowingNotifications.get(userHandle); } /** @return true if the entry needs redaction when on the lockscreen. */ @@ -451,9 +679,15 @@ public class NotificationLockscreenUserManagerImpl implements return true; } NotificationEntry entry = mCommonNotifCollectionLazy.get().getEntry(key); - return entry != null - && entry.getRanking().getLockscreenVisibilityOverride() - == Notification.VISIBILITY_PRIVATE; + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + return entry != null + && entry.getRanking().getChannel().getLockscreenVisibility() + == Notification.VISIBILITY_PRIVATE; + } else { + return entry != null + && entry.getRanking().getLockscreenVisibilityOverride() + == Notification.VISIBILITY_PRIVATE; + } } private void updateCurrentProfilesCache() { @@ -491,20 +725,6 @@ public class NotificationLockscreenUserManagerImpl implements } /** - * If any managed/work profiles are in public mode. - */ - public boolean isAnyManagedProfilePublicMode() { - synchronized (mLock) { - for (int i = mCurrentManagedProfiles.size() - 1; i >= 0; i--) { - if (isLockscreenPublicMode(mCurrentManagedProfiles.valueAt(i).id)) { - return true; - } - } - } - return false; - } - - /** * Returns the current user id. This can change if the user is switched. */ public int getCurrentUserId() { @@ -581,8 +801,16 @@ public class NotificationLockscreenUserManagerImpl implements } private void notifyNotificationStateChanged() { - for (NotificationStateChangedListener listener : mNotifStateChangedListeners) { - listener.onNotificationStateChanged(); + if (!Looper.getMainLooper().isCurrentThread()) { + mMainHandler.post(() -> { + for (NotificationStateChangedListener listener : mNotifStateChangedListeners) { + listener.onNotificationStateChanged(); + } + }); + } else { + for (NotificationStateChangedListener listener : mNotifStateChangedListeners) { + listener.onNotificationStateChanged(); + } } } @@ -620,5 +848,15 @@ public class NotificationLockscreenUserManagerImpl implements pw.println(mUsersInLockdownLatestResult); pw.print(" mShouldHideNotifsLatestResult="); pw.println(mShouldHideNotifsLatestResult); + pw.print(" mUsersDpcAllowingNotifications="); + pw.println(mUsersDpcAllowingNotifications); + pw.print(" mUsersUsersAllowingNotifications="); + pw.println(mUsersUsersAllowingNotifications); + pw.print(" mKeyguardAllowingNotifications="); + pw.println(mKeyguardAllowingNotifications); + pw.print(" mUsersDpcAllowingPrivateNotifications="); + pw.println(mUsersDpcAllowingPrivateNotifications); + pw.print(" mUsersUsersAllowingPrivateNotifications="); + pw.println(mUsersUsersAllowingPrivateNotifications); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index 37a4ef168423..ef45d2b75ba0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar; import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD; import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_TO_AOD; +import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -43,15 +44,17 @@ import com.android.internal.logging.UiEventLogger; import com.android.keyguard.KeyguardClockSwitch; import com.android.systemui.DejankUtils; import com.android.systemui.Dumpable; -import com.android.systemui.res.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; -import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.res.R; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.policy.CallbackController; import com.android.systemui.util.Compile; +import dagger.Lazy; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Comparator; @@ -95,6 +98,7 @@ public class StatusBarStateControllerImpl implements private final ArrayList<RankedListener> mListeners = new ArrayList<>(); private final UiEventLogger mUiEventLogger; private final InteractionJankMonitor mInteractionJankMonitor; + private final Lazy<ShadeInteractor> mShadeInteractorLazy; private int mState; private int mLastState; private int mUpcomingState; @@ -158,15 +162,13 @@ public class StatusBarStateControllerImpl implements UiEventLogger uiEventLogger, DumpManager dumpManager, InteractionJankMonitor interactionJankMonitor, - ShadeExpansionStateManager shadeExpansionStateManager - ) { + Lazy<ShadeInteractor> shadeInteractorLazy) { mUiEventLogger = uiEventLogger; mInteractionJankMonitor = interactionJankMonitor; + mShadeInteractorLazy = shadeInteractorLazy; for (int i = 0; i < HISTORY_SIZE; i++) { mHistoricalRecords[i] = new HistoricalState(); } - shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged); - dumpManager.registerDumpable(this); } @@ -336,6 +338,8 @@ public class StatusBarStateControllerImpl implements && (view != null && view.isAttachedToWindow())) { mView = view; mClockSwitchView = view.findViewById(R.id.keyguard_clock_container); + collectFlow(mView, mShadeInteractorLazy.get().isAnyExpanded(), + this::onShadeOrQsExpanded); } mDozeAmountTarget = dozeAmount; if (animated) { @@ -345,7 +349,7 @@ public class StatusBarStateControllerImpl implements } } - private void onShadeExpansionFullyChanged(Boolean isExpanded) { + private void onShadeOrQsExpanded(Boolean isExpanded) { if (mIsExpanded != isExpanded) { mIsExpanded = isExpanded; String tag = getClass().getSimpleName() + "#setIsExpanded"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt index b2c32cd42d1d..db7f46eb28f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt @@ -1,6 +1,5 @@ package com.android.systemui.statusbar.notification.interruption -import android.app.Notification import android.app.Notification.VISIBILITY_SECRET import android.content.Context import android.database.ContentObserver @@ -14,6 +13,8 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.NotificationLockscreenUserManager @@ -78,7 +79,8 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor( private val statusBarStateController: SysuiStatusBarStateController, private val userTracker: UserTracker, private val secureSettings: SecureSettings, - private val globalSettings: GlobalSettings + private val globalSettings: GlobalSettings, + private val featureFlags: FeatureFlagsClassic ) : CoreStartable, KeyguardNotificationVisibilityProvider { private val showSilentNotifsUri = secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS) @@ -201,7 +203,7 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor( // device isn't public, no need to check public-related settings, so allow !lockscreenUserManager.isLockscreenPublicMode(user) -> false // entry is meant to be secret on the lockscreen, disallow - entry.ranking.lockscreenVisibilityOverride == Notification.VISIBILITY_SECRET -> true + isRankingVisibilitySecret(entry) -> true // disallow if user disallows notifications in public else -> !lockscreenUserManager.userAllowsNotificationsInPublic(user) } @@ -215,6 +217,17 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor( } } + private fun isRankingVisibilitySecret(entry: NotificationEntry): Boolean { + return if (featureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + // ranking.lockscreenVisibilityOverride contains possibly out of date DPC and Setting + // info, and NotificationLockscreenUserManagerImpl is already listening for updates + // to those + entry.ranking.channel.lockscreenVisibility == VISIBILITY_SECRET + } else { + entry.ranking.lockscreenVisibilityOverride == VISIBILITY_SECRET + } + } + override fun dump(pw: PrintWriter, args: Array<out String>) = pw.asIndenting().run { println("isLockedOrLocking=$isLockedOrLocking") withIncreasedIndent { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java index 6ec9dbe003a2..b0155f13dbec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java @@ -180,4 +180,9 @@ public interface NotificationInterruptStateProvider { * Add a component that can suppress visual interruptions. */ void addSuppressor(NotificationInterruptSuppressor suppressor); + + /** + * Remove a component that can suppress visual interruptions. + */ + void removeSuppressor(NotificationInterruptSuppressor suppressor); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index 3819843aa7b2..778a0a90cd85 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -175,6 +175,11 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter } @Override + public void removeSuppressor(NotificationInterruptSuppressor suppressor) { + mSuppressors.remove(suppressor); + } + + @Override public boolean shouldBubbleUp(NotificationEntry entry) { final StatusBarNotification sbn = entry.getSbn(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt index ebdeded6e329..d7f0baf4f002 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt @@ -58,6 +58,10 @@ class NotificationInterruptStateProviderWrapper( wrapped.addSuppressor(suppressor) } + override fun removeLegacySuppressor(suppressor: NotificationInterruptSuppressor) { + wrapped.removeSuppressor(suppressor) + } + override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision = wrapped.checkHeadsUp(entry, /* log= */ false).let { DecisionImpl.of(it) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt index 454ba02b2d73..920bbe96b33b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt @@ -60,6 +60,13 @@ interface VisualInterruptionDecisionProvider { fun addLegacySuppressor(suppressor: NotificationInterruptSuppressor) /** + * Removes a [component][suppressor] that can suppress visual interruptions. + * + * @param[suppressor] the suppressor to remove + */ + fun removeLegacySuppressor(suppressor: NotificationInterruptSuppressor) + + /** * Decides whether a [notification][entry] should display as heads-up or not, but does not log * that decision. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 6b4382f731ea..407148faeb30 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -34,7 +34,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.res.R; -import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener; @@ -48,6 +48,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManagerLogger; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.policy.OnHeadsUpPhoneListenerChange; +import com.android.systemui.util.kotlin.JavaAdapter; import java.io.PrintWriter; import java.util.ArrayList; @@ -105,7 +106,8 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp /////////////////////////////////////////////////////////////////////////////////////////////// // Constructor: @Inject - public HeadsUpManagerPhone(@NonNull final Context context, + public HeadsUpManagerPhone( + @NonNull final Context context, HeadsUpManagerLogger logger, StatusBarStateController statusBarStateController, KeyguardBypassController bypassController, @@ -115,7 +117,8 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp @Main Handler handler, AccessibilityManagerWrapper accessibilityManagerWrapper, UiEventLogger uiEventLogger, - ShadeExpansionStateManager shadeExpansionStateManager) { + JavaAdapter javaAdapter, + ShadeInteractor shadeInteractor) { super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger); Resources resources = mContext.getResources(); mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time); @@ -136,8 +139,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp updateResources(); } }); - - shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged); + javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(), this::onShadeOrQsExpanded); } public void setAnimationStateHandler(AnimationStateHandler handler) { @@ -230,7 +232,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp mTrackingHeadsUp = trackingHeadsUp; } - private void onShadeExpansionFullyChanged(Boolean isExpanded) { + private void onShadeOrQsExpanded(Boolean isExpanded) { if (isExpanded != mIsExpanded) { mIsExpanded = isExpanded; if (isExpanded) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt index 4d9de09fded4..fa6d2797a37e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt @@ -3,12 +3,15 @@ package com.android.systemui.statusbar.phone import android.app.StatusBarManager import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager -import com.android.systemui.shade.ShadeExpansionStateManager +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.window.StatusBarWindowStateController import com.android.systemui.util.concurrency.DelayableExecutor +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import java.io.PrintWriter import javax.inject.Inject @@ -25,14 +28,14 @@ import javax.inject.Inject */ @SysUISingleton class StatusBarHideIconsForBouncerManager @Inject constructor( - private val commandQueue: CommandQueue, - @Main private val mainExecutor: DelayableExecutor, - statusBarWindowStateController: StatusBarWindowStateController, - shadeExpansionStateManager: ShadeExpansionStateManager, - dumpManager: DumpManager + @Application private val scope: CoroutineScope, + private val commandQueue: CommandQueue, + @Main private val mainExecutor: DelayableExecutor, + statusBarWindowStateController: StatusBarWindowStateController, + val shadeInteractor: ShadeInteractor, + dumpManager: DumpManager ) : Dumpable { // State variables set by external classes. - private var panelExpanded: Boolean = false private var isOccluded: Boolean = false private var bouncerShowing: Boolean = false private var topAppHidesStatusBar: Boolean = false @@ -49,10 +52,9 @@ class StatusBarHideIconsForBouncerManager @Inject constructor( statusBarWindowStateController.addListener { state -> setStatusBarStateAndTriggerUpdate(state) } - shadeExpansionStateManager.addFullExpansionListener { isExpanded -> - if (panelExpanded != isExpanded) { - panelExpanded = isExpanded - updateHideIconsForBouncer(animate = false) + scope.launch { + shadeInteractor.isAnyExpanded.collect { + updateHideIconsForBouncer(false) } } } @@ -101,7 +103,7 @@ class StatusBarHideIconsForBouncerManager @Inject constructor( topAppHidesStatusBar && isOccluded && (statusBarWindowHidden || bouncerShowing) - val hideBecauseKeyguard = !panelExpanded && !isOccluded && bouncerShowing + val hideBecauseKeyguard = !isShadeOrQsExpanded() && !isOccluded && bouncerShowing val shouldHideIconsForBouncer = hideBecauseApp || hideBecauseKeyguard if (hideIconsForBouncer != shouldHideIconsForBouncer) { hideIconsForBouncer = shouldHideIconsForBouncer @@ -125,9 +127,13 @@ class StatusBarHideIconsForBouncerManager @Inject constructor( } } + private fun isShadeOrQsExpanded(): Boolean { + return shadeInteractor.isAnyExpanded.value + } + override fun dump(pw: PrintWriter, args: Array<out String>) { pw.println("---- State variables set externally ----") - pw.println("panelExpanded=$panelExpanded") + pw.println("isShadeOrQsExpanded=${isShadeOrQsExpanded()}") pw.println("isOccluded=$isOccluded") pw.println("bouncerShowing=$bouncerShowing") pw.println("topAppHideStatusBar=$topAppHidesStatusBar") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java index ba73c1098637..6d8ec44ad55e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java @@ -39,6 +39,7 @@ import com.android.systemui.res.R; import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; @@ -86,8 +87,9 @@ public final class StatusBarTouchableRegionManager implements Dumpable { ConfigurationController configurationController, HeadsUpManager headsUpManager, ShadeExpansionStateManager shadeExpansionStateManager, + ShadeInteractor shadeInteractor, Provider<SceneInteractor> sceneInteractor, - Provider<JavaAdapter> javaAdapter, + JavaAdapter javaAdapter, SceneContainerFlags sceneContainerFlags, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, PrimaryBouncerInteractor primaryBouncerInteractor, @@ -126,12 +128,12 @@ public final class StatusBarTouchableRegionManager implements Dumpable { }); mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; - shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged); + javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(), this::onShadeOrQsExpanded); if (sceneContainerFlags.isEnabled()) { - javaAdapter.get().alwaysCollectFlow( + javaAdapter.alwaysCollectFlow( sceneInteractor.get().isVisible(), - this::onShadeExpansionFullyChanged); + this::onShadeOrQsExpanded); } mPrimaryBouncerInteractor = primaryBouncerInteractor; @@ -151,7 +153,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { pw.println(mTouchableRegion); } - private void onShadeExpansionFullyChanged(Boolean isExpanded) { + private void onShadeOrQsExpanded(Boolean isExpanded) { if (isExpanded != mIsStatusBarExpanded) { mIsStatusBarExpanded = isExpanded; if (isExpanded) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt index 3ae1f35b8134..da4dc850fcae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.res.R import com.android.systemui.settings.UserContextProvider import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat +import junit.framework.Assert.assertEquals import org.junit.After import org.junit.Before import org.junit.Test @@ -111,6 +112,15 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() { } @Test + fun showDialog_singleAppIsDefault() { + dialog.show() + + val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner) + val singleApp = context.getString(R.string.screen_share_permission_dialog_option_single_app) + assertEquals(spinner.adapter.getItem(0), singleApp) + } + + @Test fun showDialog_cancelClicked_dialogIsDismissed() { dialog.show() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 4f0cec5759e5..700c9ae9006a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -84,6 +84,7 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.common.ui.view.LongPressHandlingView; import com.android.systemui.doze.DozeLog; import com.android.systemui.dump.DumpManager; @@ -120,6 +121,8 @@ import com.android.systemui.plugins.qs.QS; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.qs.QSFragmentLegacy; import com.android.systemui.res.R; +import com.android.systemui.scene.SceneTestUtils; +import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.shade.data.repository.ShadeRepository; @@ -137,6 +140,7 @@ import com.android.systemui.statusbar.QsFrameTranslateController; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; +import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository; import com.android.systemui.statusbar.notification.ConversationNotificationManager; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; @@ -168,6 +172,7 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; import com.android.systemui.statusbar.phone.TapAgainViewController; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -176,8 +181,10 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController; import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; +import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.unfold.SysUIUnfoldComponent; +import com.android.systemui.user.data.repository.FakeUserRepository; import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.util.time.SystemClock; @@ -197,6 +204,8 @@ import java.util.List; import java.util.Optional; import kotlinx.coroutines.CoroutineDispatcher; +import kotlinx.coroutines.flow.StateFlowKt; +import kotlinx.coroutines.test.TestScope; public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @@ -324,7 +333,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mEmptySpaceClickListenerCaptor; @Mock protected ActivityStarter mActivityStarter; @Mock protected KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor; - @Mock private ShadeInteractor mShadeInteractor; @Mock private JavaAdapter mJavaAdapter; @Mock private CastController mCastController; @Mock private KeyguardRootView mKeyguardRootView; @@ -335,6 +343,9 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; protected FakeKeyguardRepository mFakeKeyguardRepository; protected KeyguardInteractor mKeyguardInteractor; + protected SceneTestUtils mUtils = new SceneTestUtils(this); + protected TestScope mTestScope = mUtils.getTestScope(); + protected ShadeInteractor mShadeInteractor; protected PowerInteractor mPowerInteractor; protected NotificationPanelViewController.TouchHandler mTouchHandler; protected ConfigurationController mConfigurationController; @@ -370,10 +381,31 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor(); mShadeRepository = new FakeShadeRepository(); mPowerInteractor = keyguardInteractorDeps.getPowerInteractor(); + when(mKeyguardTransitionInteractor.isInTransitionToStateWhere(any())).thenReturn( + StateFlowKt.MutableStateFlow(false)); + mShadeInteractor = new ShadeInteractor( + mTestScope.getBackgroundScope(), + new FakeDeviceProvisioningRepository(), + new FakeDisableFlagsRepository(), + mDozeParameters, + new FakeSceneContainerFlags(), + mUtils::sceneInteractor, + mFakeKeyguardRepository, + mKeyguardTransitionInteractor, + mPowerInteractor, + new FakeUserSetupRepository(), + new FakeUserRepository(), + new SharedNotificationContainerInteractor( + new FakeConfigurationRepository(), + mContext, + new ResourcesSplitShadeStateController() + ), + mShadeRepository + ); SystemClock systemClock = new FakeSystemClock(); mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager, - mInteractionJankMonitor, mShadeExpansionStateManager); + mInteractionJankMonitor, () -> mShadeInteractor); KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext); keyguardStatusView.setId(R.id.keyguard_status_view); @@ -532,7 +564,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mDumpManager, mock(HeadsUpManager.class), new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager, - mInteractionJankMonitor, mShadeExpansionStateManager), + mInteractionJankMonitor, + () -> mShadeInteractor), mKeyguardBypassController, mDozeParameters, mScreenOffAnimationController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index eb006100d535..d4fb387f9670 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -90,7 +90,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository; -import com.android.systemui.user.domain.interactor.UserInteractor; +import com.android.systemui.user.data.repository.FakeUserRepository; import com.google.common.util.concurrent.MoreExecutors; @@ -230,7 +230,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { keyguardTransitionInteractor, powerInteractor, new FakeUserSetupRepository(), - mock(UserInteractor.class), + new FakeUserRepository(), new SharedNotificationContainerInteractor( configurationRepository, mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index 65174bab7f63..8a876c80194d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -95,15 +95,16 @@ import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository; -import com.android.systemui.user.domain.interactor.UserInteractor; +import com.android.systemui.user.data.repository.FakeUserRepository; import com.android.systemui.util.kotlin.JavaAdapter; +import dagger.Lazy; + import org.junit.After; import org.junit.Before; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import dagger.Lazy; import kotlinx.coroutines.test.TestScope; public class QuickSettingsControllerBaseTest extends SysuiTestCase { @@ -162,7 +163,6 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { @Mock protected DumpManager mDumpManager; @Mock protected UiEventLogger mUiEventLogger; @Mock protected CastController mCastController; - @Mock protected UserInteractor mUserInteractor; protected FakeDisableFlagsRepository mDisableFlagsRepository = new FakeDisableFlagsRepository(); protected FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository(); @@ -186,7 +186,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); when(mPanelViewControllerLazy.get()).thenReturn(mNotificationPanelViewController); mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager, - mInteractionJankMonitor, mShadeExpansionStateManager); + mInteractionJankMonitor, () -> mShadeInteractor); FakeDeviceProvisioningRepository deviceProvisioningRepository = new FakeDeviceProvisioningRepository(); @@ -266,7 +266,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { keyguardTransitionInteractor, powerInteractor, new FakeUserSetupRepository(), - mUserInteractor, + new FakeUserRepository(), new SharedNotificationContainerInteractor( configurationRepository, mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java new file mode 100644 index 000000000000..3a9c24a7109c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java @@ -0,0 +1,597 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; +import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS; +import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS; +import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS; +import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.app.KeyguardManager; +import android.app.Notification; +import android.app.admin.DevicePolicyManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.UserInfo; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.internal.widget.LockPatternUtils; +import com.android.systemui.Dependency; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FakeFeatureFlagsClassic; +import com.android.systemui.flags.Flags; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.settings.FakeSettings; + +import com.google.android.collect.Lists; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.Executor; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class NotificationLockscreenUserManagerMainThreadTest extends SysuiTestCase { + @Mock + private NotificationPresenter mPresenter; + @Mock + private UserManager mUserManager; + @Mock + private UserTracker mUserTracker; + + // Dependency mocks: + @Mock + private NotificationVisibilityProvider mVisibilityProvider; + @Mock + private CommonNotifCollection mNotifCollection; + @Mock + private DevicePolicyManager mDevicePolicyManager; + @Mock + private NotificationClickNotifier mClickNotifier; + @Mock + private OverviewProxyService mOverviewProxyService; + @Mock + private KeyguardManager mKeyguardManager; + @Mock + private DeviceProvisionedController mDeviceProvisionedController; + @Mock + private StatusBarStateController mStatusBarStateController; + @Mock + private BroadcastDispatcher mBroadcastDispatcher; + @Mock + private KeyguardStateController mKeyguardStateController; + + private UserInfo mCurrentUser; + private UserInfo mSecondaryUser; + private UserInfo mWorkUser; + private FakeSettings mSettings; + private TestNotificationLockscreenUserManager mLockscreenUserManager; + private NotificationEntry mCurrentUserNotif; + private NotificationEntry mSecondaryUserNotif; + private NotificationEntry mWorkProfileNotif; + private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic(); + private Executor mBackgroundExecutor = Runnable::run; // Direct executor + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false); + + int currentUserId = ActivityManager.getCurrentUser(); + when(mUserTracker.getUserId()).thenReturn(currentUserId); + mSettings = new FakeSettings(); + mSettings.setUserId(ActivityManager.getCurrentUser()); + mCurrentUser = new UserInfo(currentUserId, "", 0); + mSecondaryUser = new UserInfo(currentUserId + 1, "", 0); + mWorkUser = new UserInfo(currentUserId + 2, "" /* name */, null /* iconPath */, 0, + UserManager.USER_TYPE_PROFILE_MANAGED); + + when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true); + when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList( + mCurrentUser, mWorkUser)); + when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList( + mSecondaryUser)); + mDependency.injectTestDependency(Dependency.MAIN_HANDLER, + Handler.createAsync(Looper.myLooper())); + + Notification notifWithPrivateVisibility = new Notification(); + notifWithPrivateVisibility.visibility = Notification.VISIBILITY_PRIVATE; + mCurrentUserNotif = new NotificationEntryBuilder() + .setNotification(notifWithPrivateVisibility) + .setUser(new UserHandle(mCurrentUser.id)) + .build(); + mSecondaryUserNotif = new NotificationEntryBuilder() + .setNotification(notifWithPrivateVisibility) + .setUser(new UserHandle(mSecondaryUser.id)) + .build(); + mWorkProfileNotif = new NotificationEntryBuilder() + .setNotification(notifWithPrivateVisibility) + .setUser(new UserHandle(mWorkUser.id)) + .build(); + + mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext); + mLockscreenUserManager.setUpWithPresenter(mPresenter); + } + + private void changeSetting(String setting) { + final Collection<Uri> lockScreenUris = new ArrayList<>(); + lockScreenUris.add(Settings.Secure.getUriFor(setting)); + mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false, + lockScreenUris, 0); + } + + @Test + public void testLockScreenShowNotificationsFalse() { + mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications()); + } + + @Test + public void testLockScreenShowNotificationsTrue() { + mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications()); + } + + @Test + public void testLockScreenAllowPrivateNotificationsTrue() { + mSettings.putInt(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id)); + } + + @Test + public void testLockScreenAllowPrivateNotificationsFalse() { + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mCurrentUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id)); + } + + @Test + public void testLockScreenAllowsWorkPrivateNotificationsFalse() { + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mWorkUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id)); + } + + @Test + public void testLockScreenAllowsWorkPrivateNotificationsTrue() { + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mWorkUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id)); + } + + @Test + public void testCurrentUserPrivateNotificationsNotRedacted() { + // GIVEN current user doesn't allow private notifications to show + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mCurrentUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // THEN current user's notification is redacted + assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + } + + @Test + public void testCurrentUserPrivateNotificationsRedacted() { + // GIVEN current user allows private notifications to show + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mCurrentUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // THEN current user's notification isn't redacted + assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + } + + @Test + public void testWorkPrivateNotificationsRedacted() { + // GIVEN work profile doesn't private notifications to show + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mWorkUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // THEN work profile notification is redacted + assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); + assertFalse(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic()); + } + + @Test + public void testWorkPrivateNotificationsNotRedacted() { + // GIVEN work profile allows private notifications to show + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mWorkUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // THEN work profile notification isn't redacted + assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); + assertTrue(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic()); + } + + @Test + public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() { + // GIVEN work profile allows private notifications to show but the other users don't + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mWorkUser.id); + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mCurrentUser.id); + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSecondaryUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // THEN the work profile notification doesn't need to be redacted + assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); + + // THEN the current user and secondary user notifications do need to be redacted + assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); + } + + @Test + public void testWorkProfileRedacted_otherUsersNotRedacted() { + // GIVEN work profile doesn't allow private notifications to show but the other users do + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mWorkUser.id); + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mCurrentUser.id); + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSecondaryUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // THEN the work profile notification needs to be redacted + assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); + + // THEN the current user and secondary user notifications don't need to be redacted + assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + assertFalse(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); + } + + @Test + public void testSecondaryUserNotRedacted_currentUserRedacted() { + // GIVEN secondary profile allows private notifications to show but the current user + // doesn't allow private notifications to show + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mCurrentUser.id); + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSecondaryUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // THEN the secondary profile notification still needs to be redacted because the current + // user's setting takes precedence + assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); + } + + @Test + public void testUserSwitchedCallsOnUserSwitching() { + mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanging(mSecondaryUser.id, + mContext); + verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id); + } + + @Test + public void testIsLockscreenPublicMode() { + assertFalse(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id)); + mLockscreenUserManager.setLockscreenPublicMode(true, mCurrentUser.id); + assertTrue(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id)); + } + + @Test + public void testUpdateIsPublicMode() { + when(mKeyguardStateController.isMethodSecure()).thenReturn(true); + + NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class); + mLockscreenUserManager.addNotificationStateChangedListener(listener); + mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class)); + + // first call explicitly sets user 0 to not public; notifies + mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); + assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0)); + verify(listener).onNotificationStateChanged(); + clearInvocations(listener); + + // calling again has no changes; does not notify + mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); + assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0)); + verify(listener, never()).onNotificationStateChanged(); + + // Calling again with keyguard now showing makes user 0 public; notifies + when(mKeyguardStateController.isShowing()).thenReturn(true); + mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); + assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0)); + verify(listener).onNotificationStateChanged(); + clearInvocations(listener); + + // calling again has no changes; does not notify + mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); + assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0)); + verify(listener, never()).onNotificationStateChanged(); + } + + @Test + public void testDevicePolicyDoesNotAllowNotifications() { + // User allows them + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + // DevicePolicy hides notifs on lockscreen + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) + .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + } + + @Test + public void testDevicePolicyDoesNotAllowNotifications_secondary() { + Mockito.clearInvocations(mDevicePolicyManager); + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + // DevicePolicy hides notifications + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id)) + .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mSecondaryUser.id, 0); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + } + + @Test + public void testDevicePolicy_noPrivateNotifications() { + Mockito.clearInvocations(mDevicePolicyManager); + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + // DevicePolicy hides sensitive content + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) + .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + } + + @Test + public void testDevicePolicy_noPrivateNotifications_userAll() { + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + // DevicePolicy hides sensitive content + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) + .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder() + .setNotification(new Notification()) + .setUser(UserHandle.ALL) + .build())); + } + + @Test + public void testDevicePolicyPrivateNotifications_secondary() { + Mockito.clearInvocations(mDevicePolicyManager); + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + // DevicePolicy hides sensitive content + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id)) + .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mSecondaryUser.id, 0); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); + assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); + } + + @Test + public void testHideNotifications_primary() { + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + } + + @Test + public void testHideNotifications_secondary() { + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + } + + @Test + public void testHideNotifications_secondary_userSwitch() { + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + } + + @Test + public void testShowNotifications_secondary_userSwitch() { + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); + + assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + } + + @Test + public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() { + // DevicePolicy allows notifications + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) + .thenReturn(0); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + // KeyguardManager does not allow notifications + when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false); + + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + // We shouldn't need to call this method, but getPrivateNotificationsAllowed has no + // callback, so it's only updated when the setting is + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + } + + @Test + public void testUserAllowsNotificationsInPublic_settingsChange() { + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + + // User disables + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + } + + private class TestNotificationLockscreenUserManager + extends NotificationLockscreenUserManagerImpl { + public TestNotificationLockscreenUserManager(Context context) { + super( + context, + mBroadcastDispatcher, + mDevicePolicyManager, + mUserManager, + mUserTracker, + (() -> mVisibilityProvider), + (() -> mNotifCollection), + mClickNotifier, + (() -> mOverviewProxyService), + NotificationLockscreenUserManagerMainThreadTest.this.mKeyguardManager, + mStatusBarStateController, + Handler.createAsync(Looper.myLooper()), + Handler.createAsync(Looper.myLooper()), + mBackgroundExecutor, + mDeviceProvisionedController, + mKeyguardStateController, + mSettings, + mock(DumpManager.class), + mock(LockPatternUtils.class), + mFakeFeatureFlags); + } + + public BroadcastReceiver getBaseBroadcastReceiverForTest() { + return mBaseBroadcastReceiver; + } + + public UserTracker.Callback getUserTrackerCallbackForTest() { + return mUserChangedCallback; + } + + public ContentObserver getLockscreenSettingsObserverForTest() { + return mLockscreenSettingsObserver; + } + + public ContentObserver getSettingsObserverForTest() { + return mSettingsObserver; + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java index 19863ecaf723..a5f5fc7e36f2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -16,17 +16,23 @@ package com.android.systemui.statusbar; +import static android.app.Notification.VISIBILITY_PRIVATE; +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS; import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS; import static android.os.UserHandle.USER_ALL; +import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS; import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS; import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; @@ -38,12 +44,15 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.KeyguardManager; import android.app.Notification; +import android.app.NotificationChannel; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.pm.UserInfo; import android.database.ContentObserver; +import android.net.Uri; +import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; @@ -59,6 +68,8 @@ import com.android.systemui.Dependency; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FakeFeatureFlagsClassic; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.settings.UserTracker; @@ -74,12 +85,16 @@ import com.android.systemui.util.settings.FakeSettings; import com.google.android.collect.Lists; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.Executor; + @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -121,11 +136,15 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { private NotificationEntry mCurrentUserNotif; private NotificationEntry mSecondaryUserNotif; private NotificationEntry mWorkProfileNotif; + private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic(); + private Executor mBackgroundExecutor = Runnable::run; // Direct executor @Before public void setUp() { MockitoAnnotations.initMocks(this); + mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true); + int currentUserId = ActivityManager.getCurrentUser(); when(mUserTracker.getUserId()).thenReturn(currentUserId); mSettings = new FakeSettings(); @@ -138,103 +157,144 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true); when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList( mCurrentUser, mWorkUser)); + when(mUserManager.getUsers()).thenReturn(Lists.newArrayList( + mCurrentUser, mWorkUser, mSecondaryUser)); when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList( mSecondaryUser)); mDependency.injectTestDependency(Dependency.MAIN_HANDLER, Handler.createAsync(Looper.myLooper())); Notification notifWithPrivateVisibility = new Notification(); - notifWithPrivateVisibility.visibility = Notification.VISIBILITY_PRIVATE; + notifWithPrivateVisibility.visibility = VISIBILITY_PRIVATE; mCurrentUserNotif = new NotificationEntryBuilder() .setNotification(notifWithPrivateVisibility) .setUser(new UserHandle(mCurrentUser.id)) .build(); + NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH); + channel.setLockscreenVisibility(VISIBILITY_NO_OVERRIDE); + mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking()) + .setChannel(channel) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); + when(mNotifCollection.getEntry(mCurrentUserNotif.getKey())).thenReturn(mCurrentUserNotif); mSecondaryUserNotif = new NotificationEntryBuilder() .setNotification(notifWithPrivateVisibility) .setUser(new UserHandle(mSecondaryUser.id)) .build(); + mSecondaryUserNotif.setRanking(new RankingBuilder(mSecondaryUserNotif.getRanking()) + .setChannel(channel) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); + when(mNotifCollection.getEntry( + mSecondaryUserNotif.getKey())).thenReturn(mSecondaryUserNotif); mWorkProfileNotif = new NotificationEntryBuilder() .setNotification(notifWithPrivateVisibility) .setUser(new UserHandle(mWorkUser.id)) .build(); + mWorkProfileNotif.setRanking(new RankingBuilder(mWorkProfileNotif.getRanking()) + .setChannel(channel) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); + when(mNotifCollection.getEntry(mWorkProfileNotif.getKey())).thenReturn(mWorkProfileNotif); mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext); mLockscreenUserManager.setUpWithPresenter(mPresenter); } + private void changeSetting(String setting) { + final Collection<Uri> lockScreenUris = new ArrayList<>(); + lockScreenUris.add(Settings.Secure.getUriFor(setting)); + mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false, + lockScreenUris, 0); + } + @Test public void testLockScreenShowNotificationsFalse() { mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications()); } @Test public void testLockScreenShowNotificationsTrue() { mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications()); } @Test public void testLockScreenAllowPrivateNotificationsTrue() { - mSettings.putInt(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + mSettings.putInt(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id)); } @Test public void testLockScreenAllowPrivateNotificationsFalse() { - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id)); } @Test public void testLockScreenAllowsWorkPrivateNotificationsFalse() { - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mWorkUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id)); } @Test public void testLockScreenAllowsWorkPrivateNotificationsTrue() { - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mWorkUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id)); } @Test - public void testCurrentUserPrivateNotificationsNotRedacted() { + public void testCurrentUserPrivateNotificationsRedacted() { // GIVEN current user doesn't allow private notifications to show - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); // THEN current user's notification is redacted assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); } @Test - public void testCurrentUserPrivateNotificationsRedacted() { + public void testCurrentUserPrivateNotificationsNotRedacted() { // GIVEN current user allows private notifications to show - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); // THEN current user's notification isn't redacted assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); } @Test + public void testCurrentUserPrivateNotificationsRedactedChannel() { + // GIVEN current user allows private notifications to show + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mCurrentUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // but doesn't allow it at the channel level + NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH); + channel.setLockscreenVisibility(VISIBILITY_PRIVATE); + mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking()) + .setChannel(channel) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); + // THEN the notification is redacted + assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + } + + @Test public void testWorkPrivateNotificationsRedacted() { // GIVEN work profile doesn't private notifications to show - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mWorkUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); // THEN work profile notification is redacted assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); @@ -244,9 +304,9 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Test public void testWorkPrivateNotificationsNotRedacted() { // GIVEN work profile allows private notifications to show - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mWorkUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); // THEN work profile notification isn't redacted assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); @@ -256,13 +316,15 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Test public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() { // GIVEN work profile allows private notifications to show but the other users don't - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mWorkUser.id); - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mCurrentUser.id); - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mSecondaryUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); // THEN the work profile notification doesn't need to be redacted assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); @@ -275,13 +337,15 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Test public void testWorkProfileRedacted_otherUsersNotRedacted() { // GIVEN work profile doesn't allow private notifications to show but the other users do - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mWorkUser.id); - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mCurrentUser.id); - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mSecondaryUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); // THEN the work profile notification needs to be redacted assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); @@ -295,11 +359,12 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { public void testSecondaryUserNotRedacted_currentUserRedacted() { // GIVEN secondary profile allows private notifications to show but the current user // doesn't allow private notifications to show - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mCurrentUser.id); - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mSecondaryUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); // THEN the secondary profile notification still needs to be redacted because the current // user's setting takes precedence @@ -323,6 +388,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Test public void testUpdateIsPublicMode() { when(mKeyguardStateController.isMethodSecure()).thenReturn(true); + when(mKeyguardStateController.isShowing()).thenReturn(false); NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class); mLockscreenUserManager.addNotificationStateChangedListener(listener); @@ -330,24 +396,28 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { // first call explicitly sets user 0 to not public; notifies mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0)); verify(listener).onNotificationStateChanged(); clearInvocations(listener); // calling again has no changes; does not notify mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0)); verify(listener, never()).onNotificationStateChanged(); // Calling again with keyguard now showing makes user 0 public; notifies when(mKeyguardStateController.isShowing()).thenReturn(true); mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0)); verify(listener).onNotificationStateChanged(); clearInvocations(listener); // calling again has no changes; does not notify mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0)); verify(listener, never()).onNotificationStateChanged(); } @@ -356,14 +426,14 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { public void testDevicePolicyDoesNotAllowNotifications() { // User allows them mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); // DevicePolicy hides notifs on lockscreen when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); @@ -371,19 +441,18 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); } - @Ignore("b/286230167") @Test public void testDevicePolicyDoesNotAllowNotifications_userAll() { // User allows them mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); // DevicePolicy hides notifications when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); @@ -392,98 +461,105 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { } @Test - @Ignore("b/286230167") public void testDevicePolicyDoesNotAllowNotifications_secondary() { + Mockito.clearInvocations(mDevicePolicyManager); // User allows notifications mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); // DevicePolicy hides notifications when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id)) .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mSecondaryUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); - // TODO (b/286230167): enable assertion verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt()); } @Test public void testDevicePolicy_noPrivateNotifications() { + Mockito.clearInvocations(mDevicePolicyManager); // User allows notifications mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); // DevicePolicy hides sensitive content when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); - // TODO (b/286230167): enable assertion. It's currently called 4 times. - //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt()); + verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt()); } @Test public void testDevicePolicy_noPrivateNotifications_userAll() { + NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH); + channel.setLockscreenVisibility(VISIBILITY_NO_OVERRIDE); + NotificationEntry notifEntry = new NotificationEntryBuilder() + .setNotification(new Notification()) + .setUser(UserHandle.ALL) + .build(); + notifEntry.setRanking(new RankingBuilder(notifEntry.getRanking()) + .setChannel(channel) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); + when(mNotifCollection.getEntry(notifEntry.getKey())).thenReturn(notifEntry); // User allows notifications mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); // DevicePolicy hides sensitive content when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); - assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder() - .setNotification(new Notification()) - .setUser(UserHandle.ALL) - .build())); + assertTrue(mLockscreenUserManager.needsRedaction(notifEntry)); } @Test public void testDevicePolicyPrivateNotifications_secondary() { + Mockito.clearInvocations(mDevicePolicyManager); // User allows notifications mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); // DevicePolicy hides sensitive content when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id)) .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mSecondaryUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); - // TODO (b/286230167): enable assertion. It's currently called 5 times. - //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt()); + verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt()); } @Test public void testHideNotifications_primary() { mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); } @@ -491,16 +567,17 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Test public void testHideNotifications_secondary() { mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); } - @Ignore("b/286230167") @Test public void testHideNotifications_workProfile() { - mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mWorkUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id); + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mWorkUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mWorkUser.id)); } @@ -508,67 +585,62 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Test public void testHideNotifications_secondary_userSwitch() { mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); - TestNotificationLockscreenUserManager lockscreenUserManager - = new TestNotificationLockscreenUserManager(mContext); - lockscreenUserManager.setUpWithPresenter(mPresenter); - - lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); + mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); - assertFalse(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); } @Test public void testShowNotifications_secondary_userSwitch() { mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); - TestNotificationLockscreenUserManager lockscreenUserManager - = new TestNotificationLockscreenUserManager(mContext); - lockscreenUserManager.setUpWithPresenter(mPresenter); - - lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); + mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); - assertTrue(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); } - @Ignore("b/286230167") @Test public void testShouldShowLockscreenNotifications_keyguardManagerNoPrivateNotifications() { + // KeyguardManager does not allow notifications + when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false); // User allows notifications mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); // DevicePolicy allows notifications when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) .thenReturn(0); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); - // KeyguardManager does not - when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false); - assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications()); } @Test public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() { - // User allows notifications - mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); // DevicePolicy allows notifications when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) .thenReturn(0); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); - // KeyguardManager does not + // KeyguardManager does not allow notifications when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false); + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + // We shouldn't need to call this method, but getPrivateNotificationsAllowed has no + // callback, so it's only updated when the setting is + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); } @@ -576,31 +648,30 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { public void testUserAllowsNotificationsInPublic_settingsChange() { // User allows notifications mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); // User disables mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); } - @Ignore("b/286230167") @Test public void testUserAllowsNotificationsInPublic_devicePolicyChange() { // User allows notifications mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); // DevicePolicy disables notifications when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); @@ -608,6 +679,28 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); } + @Test + public void testNewUserAdded() { + int newUserId = 14; + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, newUserId); + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, newUserId); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, newUserId)) + .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + + BroadcastReceiver broadcastReceiver = + mLockscreenUserManager.getBaseBroadcastReceiverForTest(); + final Bundle extras = new Bundle(); + final Intent intent = new Intent(Intent.ACTION_USER_ADDED); + intent.putExtras(extras); + intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId); + broadcastReceiver.onReceive(mContext, intent); + + verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), eq(newUserId)); + + assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(newUserId)); + assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(newUserId)); + } + private class TestNotificationLockscreenUserManager extends NotificationLockscreenUserManagerImpl { public TestNotificationLockscreenUserManager(Context context) { @@ -623,12 +716,17 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { (() -> mOverviewProxyService), NotificationLockscreenUserManagerTest.this.mKeyguardManager, mStatusBarStateController, - Handler.createAsync(Looper.myLooper()), + Handler.createAsync(TestableLooper.get( + NotificationLockscreenUserManagerTest.this).getLooper()), + Handler.createAsync(TestableLooper.get( + NotificationLockscreenUserManagerTest.this).getLooper()), + mBackgroundExecutor, mDeviceProvisionedController, mKeyguardStateController, mSettings, mock(DumpManager.class), - mock(LockPatternUtils.class)); + mock(LockPatternUtils.class), + mFakeFeatureFlags); } public BroadcastReceiver getBaseBroadcastReceiverForTest() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index 3327e42b930f..1b26e19aa4a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -23,9 +23,33 @@ import androidx.test.filters.SmallTest import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.classifier.FalsingCollectorFake +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.keyguard.data.repository.FakeCommandQueue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.shade.ShadeExpansionStateManager +import com.android.systemui.power.data.repository.FakePowerRepository +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository +import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController +import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.util.mockito.mock import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -40,17 +64,22 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.mock import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper class StatusBarStateControllerImplTest : SysuiTestCase() { + private val utils = SceneTestUtils(this) + private val testScope = utils.testScope + private lateinit var shadeInteractor: ShadeInteractor + private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor + private lateinit var fromPrimaryBouncerTransitionInteractor: + FromPrimaryBouncerTransitionInteractor @Mock lateinit var interactionJankMonitor: InteractionJankMonitor - @Mock private lateinit var mockDarkAnimator: ObjectAnimator - @Mock private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager + @Mock lateinit var mockDarkAnimator: ObjectAnimator private lateinit var controller: StatusBarStateControllerImpl private lateinit var uiEventLogger: UiEventLoggerFake @@ -65,10 +94,74 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { controller = object : StatusBarStateControllerImpl( uiEventLogger, mock(DumpManager::class.java), - interactionJankMonitor, shadeExpansionStateManager + interactionJankMonitor, + { shadeInteractor } ) { override fun createDarkAnimator(): ObjectAnimator { return mockDarkAnimator } } + + val powerInteractor = PowerInteractor( + FakePowerRepository(), + FalsingCollectorFake(), + mock(), + controller) + val keyguardRepository = FakeKeyguardRepository() + val keyguardTransitionRepository = FakeKeyguardTransitionRepository() + val featureFlags = FakeFeatureFlagsClassic() + val shadeRepository = FakeShadeRepository() + val sceneContainerFlags = FakeSceneContainerFlags() + val configurationRepository = FakeConfigurationRepository() + val keyguardInteractor = KeyguardInteractor( + keyguardRepository, + FakeCommandQueue(), + powerInteractor, + featureFlags, + sceneContainerFlags, + FakeDeviceEntryRepository(), + FakeKeyguardBouncerRepository(), + configurationRepository, + shadeRepository, + utils::sceneInteractor) + val keyguardTransitionInteractor = KeyguardTransitionInteractor( + testScope.backgroundScope, + keyguardTransitionRepository, + { keyguardInteractor }, + { fromLockscreenTransitionInteractor }, + { fromPrimaryBouncerTransitionInteractor }) + fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor( + keyguardTransitionRepository, + keyguardTransitionInteractor, + testScope.backgroundScope, + keyguardInteractor, + featureFlags, + shadeRepository, + powerInteractor) + fromPrimaryBouncerTransitionInteractor = FromPrimaryBouncerTransitionInteractor( + keyguardTransitionRepository, + keyguardTransitionInteractor, + testScope.backgroundScope, + keyguardInteractor, + featureFlags, + mock(), + powerInteractor) + shadeInteractor = ShadeInteractor( + testScope.backgroundScope, + FakeDeviceProvisioningRepository(), + FakeDisableFlagsRepository(), + mock(), + sceneContainerFlags, + utils::sceneInteractor, + keyguardRepository, + keyguardTransitionInteractor, + powerInteractor, + FakeUserSetupRepository(), + FakeUserRepository(), + SharedNotificationContainerInteractor( + configurationRepository, + mContext, + ResourcesSplitShadeStateController()), + shadeRepository, + ) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java index 83f2a5d4fbdf..b922ab39912b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java @@ -22,6 +22,7 @@ import static android.app.Notification.VISIBILITY_SECRET; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MIN; +import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; @@ -36,6 +37,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.NotificationChannel; import android.content.Context; import android.os.Handler; import android.os.UserHandle; @@ -51,6 +53,9 @@ import com.android.systemui.CoreStartable; import com.android.systemui.SysuiTestCase; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FakeFeatureFlagsClassic; +import com.android.systemui.flags.FeatureFlagsClassic; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -96,6 +101,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @Mock private UserTracker mUserTracker; private final FakeSettings mSecureSettings = new FakeSettings(); private final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings(); + private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic(); private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider; private NotificationEntry mEntry; @@ -116,7 +122,8 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { mStatusBarStateController, mUserTracker, mSecureSettings, - mGlobalSettings); + mGlobalSettings, + mFeatureFlags); mKeyguardNotificationVisibilityProvider = component.getProvider(); for (CoreStartable startable : component.getCoreStartables().values()) { startable.start(); @@ -424,6 +431,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @Test public void publicMode_settingsDisallow() { + mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true); // GIVEN an 'unfiltered-keyguard-showing' state setupUnfilteredState(mEntry); @@ -433,12 +441,59 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID)) .thenReturn(false); + mEntry.setRanking(new RankingBuilder() + .setChannel(new NotificationChannel("1", "1", 4)) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE) + .setKey(mEntry.getKey()).build()); + + // THEN filter out the entry + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); + } + + @Test + public void publicMode_settingsDisallow_mainThread() { + mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false); + // GIVEN an 'unfiltered-keyguard-showing' state + setupUnfilteredState(mEntry); + + // WHEN the notification's user is in public mode and settings are configured to disallow + // notifications in public mode + when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true); + when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID)) + .thenReturn(false); + + mEntry.setRanking(new RankingBuilder() + .setChannel(new NotificationChannel("1", "1", 4)) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE) + .setKey(mEntry.getKey()).build()); + // THEN filter out the entry assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); } @Test public void publicMode_notifDisallowed() { + mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true); + NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH); + channel.setLockscreenVisibility(VISIBILITY_SECRET); + // GIVEN an 'unfiltered-keyguard-showing' state + setupUnfilteredState(mEntry); + + // WHEN the notification's user is in public mode and settings are configured to disallow + // notifications in public mode + when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true); + mEntry.setRanking(new RankingBuilder() + .setKey(mEntry.getKey()) + .setChannel(channel) + .setVisibilityOverride(VISIBILITY_SECRET).build()); + + // THEN filter out the entry + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); + } + + @Test + public void publicMode_notifDisallowed_mainThread() { + mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false); // GIVEN an 'unfiltered-keyguard-showing' state setupUnfilteredState(mEntry); @@ -506,6 +561,54 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { } @Test + public void notificationChannelVisibilityNoOverride() { + mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true); + // GIVEN a VISIBILITY_PRIVATE notification + NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder() + .setUser(new UserHandle(NOTIF_USER_ID)); + entryBuilder.modifyNotification(mContext) + .setVisibility(VISIBILITY_PRIVATE); + mEntry = entryBuilder.build(); + // ranking says secret because of DPC or Setting + mEntry.setRanking(new RankingBuilder() + .setKey(mEntry.getKey()) + .setVisibilityOverride(VISIBILITY_SECRET) + .setImportance(IMPORTANCE_HIGH) + .build()); + + // WHEN we're in an 'unfiltered-keyguard-showing' state + setupUnfilteredState(mEntry); + + // THEN don't hide the entry based on visibility. (Redaction is handled elsewhere.) + assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); + } + + @Test + public void notificationChannelVisibilitySecret() { + mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true); + // GIVEN a VISIBILITY_PRIVATE notification + NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder() + .setUser(new UserHandle(NOTIF_USER_ID)); + entryBuilder.modifyNotification(mContext) + .setVisibility(VISIBILITY_PRIVATE); + // And a VISIBILITY_SECRET NotificationChannel + NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_HIGH); + channel.setLockscreenVisibility(VISIBILITY_SECRET); + mEntry = entryBuilder.build(); + // WHEN we're in an 'unfiltered-keyguard-showing' state + setupUnfilteredState(mEntry); + when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true); + when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true); + + mEntry.setRanking(new RankingBuilder(mEntry.getRanking()) + .setChannel(channel) + .build()); + + // THEN hide the entry based on visibility. + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); + } + + @Test public void notificationVisibilityPrivate() { // GIVEN a VISIBILITY_PRIVATE notification NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder() @@ -635,7 +738,8 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @BindsInstance SysuiStatusBarStateController statusBarStateController, @BindsInstance UserTracker userTracker, @BindsInstance SecureSettings secureSettings, - @BindsInstance GlobalSettings globalSettings + @BindsInstance GlobalSettings globalSettings, + @BindsInstance FeatureFlagsClassic featureFlags ); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java index cda2a74609bd..48b95d407246 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java @@ -34,7 +34,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; -import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.AlertingNotificationManager; import com.android.systemui.statusbar.AlertingNotificationManagerTest; import com.android.systemui.statusbar.NotificationShadeWindowController; @@ -45,6 +45,7 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.HeadsUpManagerLogger; +import com.android.systemui.util.kotlin.JavaAdapter; import org.junit.After; import org.junit.Before; @@ -56,6 +57,8 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import kotlinx.coroutines.flow.StateFlowKt; + @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -70,8 +73,9 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { @Mock private KeyguardBypassController mBypassController; @Mock private ConfigurationControllerImpl mConfigurationController; @Mock private AccessibilityManagerWrapper mAccessibilityManagerWrapper; - @Mock private ShadeExpansionStateManager mShadeExpansionStateManager; @Mock private UiEventLogger mUiEventLogger; + @Mock private JavaAdapter mJavaAdapter; + @Mock private ShadeInteractor mShadeInteractor; private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone { TestableHeadsUpManagerPhone( @@ -85,7 +89,8 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { Handler handler, AccessibilityManagerWrapper accessibilityManagerWrapper, UiEventLogger uiEventLogger, - ShadeExpansionStateManager shadeExpansionStateManager + JavaAdapter javaAdapter, + ShadeInteractor shadeInteractor ) { super( context, @@ -98,7 +103,8 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { handler, accessibilityManagerWrapper, uiEventLogger, - shadeExpansionStateManager + javaAdapter, + shadeInteractor ); mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME; mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME; @@ -117,7 +123,8 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { mTestHandler, mAccessibilityManagerWrapper, mUiEventLogger, - mShadeExpansionStateManager + mJavaAdapter, + mShadeInteractor ); } @@ -129,6 +136,7 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { @Before @Override public void setUp() { + when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false)); final AccessibilityManagerWrapper accessibilityMgr = mDependency.injectMockDependency(AccessibilityManagerWrapper.class); when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt())) diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index c8327029026d..123362ac57d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -156,7 +156,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository; -import com.android.systemui.user.domain.interactor.UserInteractor; +import com.android.systemui.user.data.repository.FakeUserRepository; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.bubbles.Bubble; @@ -450,7 +450,7 @@ public class BubblesTest extends SysuiTestCase { keyguardTransitionInteractor, powerInteractor, new FakeUserSetupRepository(), - mock(UserInteractor.class), + new FakeUserRepository(), new SharedNotificationContainerInteractor( configurationRepository, mContext, diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 1ba1f55af71b..16e3fdf2a6ab 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -163,9 +163,12 @@ public class SettingsToPropertiesMapper { "wear_system_health", "wear_systems", "window_surfaces", - "windowing_frontend" + "windowing_frontend", }; + public static final String NAMESPACE_REBOOT_STAGING = "staged"; + public static final String NAMESPACE_REBOOT_STAGING_DELIMITER = "*"; + private final String[] mGlobalSettings; private final String[] mDeviceConfigScopes; @@ -261,6 +264,22 @@ public class SettingsToPropertiesMapper { } }); } + + // add sys prop sync callback for staged flag values + DeviceConfig.addOnPropertiesChangedListener( + NAMESPACE_REBOOT_STAGING, + AsyncTask.THREAD_POOL_EXECUTOR, + (DeviceConfig.Properties properties) -> { + String scope = properties.getNamespace(); + for (String key : properties.getKeyset()) { + String aconfigPropertyName = makeAconfigFlagStagedPropertyName(key); + if (aconfigPropertyName == null) { + log("unable to construct system property for " + scope + "/" + key); + return; + } + setProperty(aconfigPropertyName, properties.getString(key, null)); + } + }); } public static SettingsToPropertiesMapper start(ContentResolver contentResolver) { @@ -332,6 +351,35 @@ public class SettingsToPropertiesMapper { } /** + * system property name constructing rule for staged aconfig flags, the flag name + * is in the form of [namespace]*[actual flag name], we should push the following + * to system properties + * "next_boot.[actual sys prop name]". + * If the name contains invalid characters or substrings for system property name, + * will return null. + * @param flagName + * @return + */ + @VisibleForTesting + static String makeAconfigFlagStagedPropertyName(String flagName) { + int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER); + if (idx == -1 || idx == flagName.length() - 1 || idx == 0) { + log("invalid staged flag: " + flagName); + return null; + } + + String propertyName = "next_boot." + makeAconfigFlagPropertyName( + flagName.substring(0, idx), flagName.substring(idx+1)); + + if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX) + || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) { + return null; + } + + return propertyName; + } + + /** * system property name constructing rule for aconfig flags: * "persist.device_config.aconfig_flags.[category_name].[flag_name]". * If the name contains invalid characters or substrings for system property name, diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig index b537e0eae548..b12d831ffe24 100644 --- a/services/core/java/com/android/server/biometrics/biometrics.aconfig +++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig @@ -6,3 +6,10 @@ flag { description: "This flag controls tunscany virtual HAL feature" bug: "294254230" } + +flag { + name: "de_hidl" + namespace: "biometrics_framework" + description: "feature flag for biometrics de-hidl" + bug: "287332354" +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index 78c95ad4576b..a47135fd032f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -35,6 +35,7 @@ import android.util.EventLog; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; @@ -116,7 +117,24 @@ public abstract class AuthenticationClient<T, O extends AuthenticateOptions> @LockoutTracker.LockoutMode public int handleFailedAttempt(int userId) { - return LockoutTracker.LOCKOUT_NONE; + if (Flags.deHidl()) { + if (mLockoutTracker != null) { + mLockoutTracker.addFailedAttemptForUser(getTargetUserId()); + } + @LockoutTracker.LockoutMode final int lockoutMode = + getLockoutTracker().getLockoutModeForUser(userId); + final PerformanceTracker performanceTracker = + PerformanceTracker.getInstanceForSensorId(getSensorId()); + if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) { + performanceTracker.incrementPermanentLockoutForUser(userId); + } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) { + performanceTracker.incrementTimedLockoutForUser(userId); + } + + return lockoutMode; + } else { + return LockoutTracker.LOCKOUT_NONE; + } } protected long getStartTimeMs() { 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 8a54ae5a6fea..037ea38a2d17 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -586,4 +586,13 @@ public class BiometricScheduler { } }, 10000); } + + /** + * Handle stop user client when user switching occurs. + */ + public void onUserStopped() {} + + public Handler getHandler() { + return mHandler; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java b/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java index 95c49032c029..35e9bcbbf085 100644 --- a/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java +++ b/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java @@ -32,6 +32,7 @@ public class LockoutCache implements LockoutTracker { mUserLockoutStates = new SparseIntArray(); } + @Override public void setLockoutModeForUser(int userId, @LockoutMode int mode) { Slog.d(TAG, "Lockout for user: " + userId + " is " + mode); synchronized (this) { diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java b/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java index 4a59c9df9bef..8271380010e2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java +++ b/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java @@ -35,4 +35,7 @@ public interface LockoutTracker { @interface LockoutMode {} @LockoutMode int getLockoutModeForUser(int userId); + void setLockoutModeForUser(int userId, @LockoutMode int mode); + default void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {} + default void addFailedAttemptForUser(int userId) {} } 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 694dfd28d0cc..80754702415a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java @@ -165,6 +165,7 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { } } + @Override public void onUserStopped() { if (mStopUserClient == null) { Slog.e(getTag(), "Unexpected onUserStopped"); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java index cc0022703745..e5d4a635876d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java @@ -31,6 +31,11 @@ public class LockoutHalImpl implements LockoutTracker { return mCurrentUserLockoutMode; } + @Override + public void setLockoutModeForUser(int userId, @LockoutMode int mode) { + setCurrentUserLockoutMode(mode); + } + public void setCurrentUserLockoutMode(@LockoutMode int lockoutMode) { mCurrentUserLockoutMode = lockoutMode; } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java index 573c20fe041b..d149f5215a7a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java @@ -38,7 +38,7 @@ import android.util.Slog; /** * Utilities for converting from hardware to framework-defined AIDL models. */ -final class AidlConversionUtils { +public final class AidlConversionUtils { private static final String TAG = "AidlConversionUtils"; diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java new file mode 100644 index 000000000000..57f5b34c197a --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.biometrics.face.AuthenticationFrame; +import android.hardware.biometrics.face.EnrollmentFrame; +import android.hardware.biometrics.face.Error; +import android.hardware.biometrics.face.ISessionCallback; +import android.hardware.face.Face; +import android.hardware.keymaster.HardwareAuthToken; +import android.util.Slog; + +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.Utils; +import com.android.server.biometrics.sensors.AcquisitionClient; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.AuthenticationConsumer; +import com.android.server.biometrics.sensors.BaseClientMonitor; +import com.android.server.biometrics.sensors.BiometricScheduler; +import com.android.server.biometrics.sensors.EnumerateConsumer; +import com.android.server.biometrics.sensors.ErrorConsumer; +import com.android.server.biometrics.sensors.LockoutConsumer; +import com.android.server.biometrics.sensors.LockoutResetDispatcher; +import com.android.server.biometrics.sensors.LockoutTracker; +import com.android.server.biometrics.sensors.RemovalConsumer; +import com.android.server.biometrics.sensors.face.FaceUtils; + +import java.util.ArrayList; +import java.util.function.Consumer; + +/** + * Response handler for the {@link ISessionCallback} HAL AIDL interface. + */ +public class AidlResponseHandler extends ISessionCallback.Stub { + /** + * Interface to send results to the AidlResponseHandler's owner. + */ + public interface HardwareUnavailableCallback { + /** + * Invoked when the HAL sends ERROR_HW_UNAVAILABLE. + */ + void onHardwareUnavailable(); + } + + private static final String TAG = "AidlResponseHandler"; + + @NonNull + private final Context mContext; + @NonNull + private final BiometricScheduler mScheduler; + private final int mSensorId; + private final int mUserId; + @NonNull + private final LockoutTracker mLockoutCache; + @NonNull + private final LockoutResetDispatcher mLockoutResetDispatcher; + + @NonNull + private final AuthSessionCoordinator mAuthSessionCoordinator; + @NonNull + private final HardwareUnavailableCallback mHardwareUnavailableCallback; + + public AidlResponseHandler(@NonNull Context context, + @NonNull BiometricScheduler scheduler, int sensorId, int userId, + @NonNull LockoutTracker lockoutTracker, + @NonNull LockoutResetDispatcher lockoutResetDispatcher, + @NonNull AuthSessionCoordinator authSessionCoordinator, + @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) { + mContext = context; + mScheduler = scheduler; + mSensorId = sensorId; + mUserId = userId; + mLockoutCache = lockoutTracker; + mLockoutResetDispatcher = lockoutResetDispatcher; + mAuthSessionCoordinator = authSessionCoordinator; + mHardwareUnavailableCallback = hardwareUnavailableCallback; + } + + @Override + public int getInterfaceVersion() { + return this.VERSION; + } + + @Override + public String getInterfaceHash() { + return this.HASH; + } + + @Override + public void onChallengeGenerated(long challenge) { + handleResponse(FaceGenerateChallengeClient.class, (c) -> c.onChallengeGenerated(mSensorId, + mUserId, challenge), null); + } + + @Override + public void onChallengeRevoked(long challenge) { + handleResponse(FaceRevokeChallengeClient.class, (c) -> c.onChallengeRevoked(mSensorId, + mUserId, challenge), null); + } + + @Override + public void onAuthenticationFrame(AuthenticationFrame frame) { + handleResponse(FaceAuthenticationClient.class, (c) -> { + if (frame == null) { + Slog.e(TAG, "Received null enrollment frame for face authentication client."); + return; + } + c.onAuthenticationFrame(AidlConversionUtils.toFrameworkAuthenticationFrame(frame)); + }, null); + } + + @Override + public void onEnrollmentFrame(EnrollmentFrame frame) { + handleResponse(FaceEnrollClient.class, (c) -> { + if (frame == null) { + Slog.e(TAG, "Received null enrollment frame for face enroll client."); + return; + } + c.onEnrollmentFrame(AidlConversionUtils.toFrameworkEnrollmentFrame(frame)); + }, null); + } + + @Override + public void onError(byte error, int vendorCode) { + onError(AidlConversionUtils.toFrameworkError(error), vendorCode); + } + + /** + * Handle error messages from the HAL. + */ + public void onError(int error, int vendorCode) { + handleResponse(ErrorConsumer.class, (c) -> { + c.onError(error, vendorCode); + if (error == Error.HW_UNAVAILABLE) { + mHardwareUnavailableCallback.onHardwareUnavailable(); + } + }, null); + } + + @Override + public void onEnrollmentProgress(int enrollmentId, int remaining) { + BaseClientMonitor client = mScheduler.getCurrentClient(); + final int currentUserId; + if (client == null) { + return; + } else { + currentUserId = client.getTargetUserId(); + } + final CharSequence name = FaceUtils.getInstance(mSensorId) + .getUniqueName(mContext, currentUserId); + final Face face = new Face(name, enrollmentId, mSensorId); + + handleResponse(FaceEnrollClient.class, (c) -> c.onEnrollResult(face, remaining), null); + } + + @Override + public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) { + final Face face = new Face("" /* name */, enrollmentId, mSensorId); + final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat); + final ArrayList<Byte> byteList = new ArrayList<>(); + for (byte b : byteArray) { + byteList.add(b); + } + handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face, + true /* authenticated */, byteList), null); + } + + @Override + public void onAuthenticationFailed() { + final Face face = new Face("" /* name */, 0 /* faceId */, mSensorId); + handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face, + false /* authenticated */, null /* hat */), null); + } + + @Override + public void onLockoutTimed(long durationMillis) { + handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis), null); + } + + @Override + public void onLockoutPermanent() { + handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null); + } + + @Override + public void onLockoutCleared() { + handleResponse(FaceResetLockoutClient.class, FaceResetLockoutClient::onLockoutCleared, + (c) -> FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId, + mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, + Utils.getCurrentStrength(mSensorId), -1 /* requestId */)); + } + + @Override + public void onInteractionDetected() { + handleResponse(FaceDetectClient.class, FaceDetectClient::onInteractionDetected, null); + } + + @Override + public void onEnrollmentsEnumerated(int[] enrollmentIds) { + if (enrollmentIds.length > 0) { + for (int i = 0; i < enrollmentIds.length; ++i) { + final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId); + final int finalI = i; + handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(face, + enrollmentIds.length - finalI - 1), null); + } + } else { + handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult( + null /* identifier */, 0 /* remaining */), null); + } + } + + @Override + public void onFeaturesRetrieved(byte[] features) { + handleResponse(FaceGetFeatureClient.class, (c) -> c.onFeatureGet(true /* success */, + features), null); + } + + @Override + public void onFeatureSet(byte feature) { + handleResponse(FaceSetFeatureClient.class, (c) -> c.onFeatureSet(true /* success */), null); + } + + @Override + public void onEnrollmentsRemoved(int[] enrollmentIds) { + if (enrollmentIds.length > 0) { + for (int i = 0; i < enrollmentIds.length; i++) { + final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId); + final int finalI = i; + handleResponse(RemovalConsumer.class, + (c) -> c.onRemoved(face, enrollmentIds.length - finalI - 1), + null); + } + } else { + handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null /* identifier */, + 0 /* remaining */), null); + } + } + + @Override + public void onAuthenticatorIdRetrieved(long authenticatorId) { + handleResponse(FaceGetAuthenticatorIdClient.class, (c) -> c.onAuthenticatorIdRetrieved( + authenticatorId), null); + } + + @Override + public void onAuthenticatorIdInvalidated(long newAuthenticatorId) { + handleResponse(FaceInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated( + newAuthenticatorId), null); + } + + /** + * Handles acquired messages sent by the HAL (specifically for HIDL HAL). + */ + public void onAcquired(int acquiredInfo, int vendorCode) { + handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode), + null); + } + + /** + * Handles lockout changed messages sent by the HAL (specifically for HIDL HAL). + */ + public void onLockoutChanged(long duration) { + mScheduler.getHandler().post(() -> { + @LockoutTracker.LockoutMode final int lockoutMode; + if (duration == 0) { + lockoutMode = LockoutTracker.LOCKOUT_NONE; + } else if (duration == -1 || duration == Long.MAX_VALUE) { + lockoutMode = LockoutTracker.LOCKOUT_PERMANENT; + } else { + lockoutMode = LockoutTracker.LOCKOUT_TIMED; + } + + mLockoutCache.setLockoutModeForUser(mUserId, lockoutMode); + + if (duration == 0) { + mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId); + } + }); + } + + private <T> void handleResponse(@NonNull Class<T> className, + @NonNull Consumer<T> actionIfClassMatchesClient, + @Nullable Consumer<BaseClientMonitor> alternateAction) { + mScheduler.getHandler().post(() -> { + final BaseClientMonitor client = mScheduler.getCurrentClient(); + if (className.isInstance(client)) { + actionIfClassMatchesClient.accept((T) client); + } else { + Slog.d(TAG, "Current client is not an instance of " + className.getName()); + if (alternateAction != null) { + alternateAction.accept(client); + } + } + }); + } + + @Override + public void onSessionClosed() { + mScheduler.getHandler().post(mScheduler::onUserStopped); + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java index 29eee6b5bb06..858bb864d4db 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java @@ -16,10 +16,14 @@ package com.android.server.biometrics.sensors.face.aidl; -import static com.android.server.biometrics.sensors.face.aidl.Sensor.HalSessionCallback; - import android.annotation.NonNull; +import android.content.Context; import android.hardware.biometrics.face.ISession; +import android.hardware.biometrics.face.V1_0.IBiometricsFace; + +import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter; + +import java.util.function.Supplier; /** * A holder for an AIDL {@link ISession} with additional metadata about the current user @@ -31,14 +35,22 @@ public class AidlSession { @NonNull private final ISession mSession; private final int mUserId; - @NonNull private final HalSessionCallback mHalSessionCallback; + @NonNull private final AidlResponseHandler mAidlResponseHandler; public AidlSession(int halInterfaceVersion, @NonNull ISession session, int userId, - HalSessionCallback halSessionCallback) { + AidlResponseHandler aidlResponseHandler) { mHalInterfaceVersion = halInterfaceVersion; mSession = session; mUserId = userId; - mHalSessionCallback = halSessionCallback; + mAidlResponseHandler = aidlResponseHandler; + } + + public AidlSession(Context context, Supplier<IBiometricsFace> session, int userId, + AidlResponseHandler aidlResponseHandler) { + mSession = new AidlToHidlAdapter(context, session, userId, aidlResponseHandler); + mHalInterfaceVersion = 0; + mUserId = userId; + mAidlResponseHandler = aidlResponseHandler; } /** The underlying {@link ISession}. */ @@ -52,8 +64,8 @@ public class AidlSession { } /** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */ - HalSessionCallback getHalSessionCallback() { - return mHalSessionCallback; + AidlResponseHandler getHalSessionCallback() { + return mAidlResponseHandler; } /** diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index 35fc43ae5f74..470dc4b7172c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -46,8 +46,8 @@ import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; -import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutConsumer; +import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.PerformanceTracker; import com.android.server.biometrics.sensors.face.UsageStats; @@ -57,7 +57,8 @@ import java.util.function.Supplier; /** * Face-specific authentication client for the {@link IFace} AIDL HAL interface. */ -class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAuthenticateOptions> +public class FaceAuthenticationClient + extends AuthenticationClient<AidlSession, FaceAuthenticateOptions> implements LockoutConsumer { private static final String TAG = "FaceAuthenticationClient"; @@ -74,11 +75,11 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut @Nullable private ICancellationSignal mCancellationSignal; @Nullable - private SensorPrivacyManager mSensorPrivacyManager; + private final SensorPrivacyManager mSensorPrivacyManager; @FaceManager.FaceAcquired private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN; - FaceAuthenticationClient(@NonNull Context context, + public FaceAuthenticationClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId, @NonNull ClientMonitorCallbackConverter listener, long operationId, @@ -86,7 +87,7 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut boolean requireConfirmation, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, boolean isStrongBiometric, @NonNull UsageStats usageStats, - @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication, + @NonNull LockoutTracker lockoutCache, boolean allowBackgroundAuthentication, @Authenticators.Types int sensorStrength) { this(context, lazyDaemon, token, requestId, listener, operationId, restricted, options, cookie, requireConfirmation, logger, biometricContext, @@ -103,12 +104,12 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut boolean requireConfirmation, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, boolean isStrongBiometric, @NonNull UsageStats usageStats, - @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication, + @NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication, SensorPrivacyManager sensorPrivacyManager, @Authenticators.Types int biometricStrength) { super(context, lazyDaemon, token, listener, operationId, restricted, options, cookie, requireConfirmation, logger, biometricContext, - isStrongBiometric, null /* taskStackListener */, null /* lockoutCache */, + isStrongBiometric, null /* taskStackListener */, lockoutTracker, allowBackgroundAuthentication, false /* shouldVibrate */, biometricStrength); setRequestId(requestId); @@ -263,8 +264,13 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut mLastAcquire = acquireInfo; final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode); onAcquiredInternal(acquireInfo, vendorCode, shouldSend); - PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId()); - pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation()); + + //Check if it is AIDL (lockoutTracker = null) or if it there is no lockout for HIDL + if (getLockoutTracker() == null || getLockoutTracker().getLockoutModeForUser( + getTargetUserId()) == LockoutTracker.LOCKOUT_NONE) { + PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId()); + pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation()); + } } /** 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 f55cf0549382..dbed1f7a8f9d 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 @@ -85,7 +85,7 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> { } }; - FaceEnrollClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, + public FaceEnrollClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, long requestId, @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java index 165c3a241043..e404bd2be31e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java @@ -36,7 +36,7 @@ import java.util.function.Supplier; public class FaceGenerateChallengeClient extends GenerateChallengeClient<AidlSession> { private static final String TAG = "FaceGenerateChallengeClient"; - FaceGenerateChallengeClient(@NonNull Context context, + public FaceGenerateChallengeClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java index ef3b345402bf..c15049b48bb2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.face.IFace; +import android.hardware.biometrics.face.ISession; import android.os.IBinder; import android.os.RemoteException; import android.provider.Settings; @@ -33,6 +34,7 @@ import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ErrorConsumer; import com.android.server.biometrics.sensors.HalClientMonitor; +import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter; import java.util.HashMap; import java.util.Map; @@ -46,14 +48,16 @@ public class FaceGetFeatureClient extends HalClientMonitor<AidlSession> implemen private static final String TAG = "FaceGetFeatureClient"; private final int mUserId; + private final int mFeature; - FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, + public FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, - @NonNull BiometricContext biometricContext) { + @NonNull BiometricContext biometricContext, int feature) { super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId, logger, biometricContext); mUserId = userId; + mFeature = feature; } @Override @@ -70,7 +74,11 @@ public class FaceGetFeatureClient extends HalClientMonitor<AidlSession> implemen @Override protected void startHalOperation() { try { - getFreshDaemon().getSession().getFeatures(); + ISession session = getFreshDaemon().getSession(); + if (session instanceof AidlToHidlAdapter) { + ((AidlToHidlAdapter) session).setFeature(mFeature); + } + session.getFeatures(); } catch (RemoteException e) { Slog.e(TAG, "Unable to getFeature", e); mCallback.onClientFinished(this, false /* success */); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java index f09d192966f1..e75c6aba1489 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java @@ -38,9 +38,9 @@ import java.util.function.Supplier; /** * Face-specific internal cleanup client for the {@link IFace} AIDL HAL interface. */ -class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlSession> { +public class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlSession> { - FaceInternalCleanupClient(@NonNull Context context, + public FaceInternalCleanupClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, 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 cc3118cc3433..dd9c6d50ae9e 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 @@ -493,7 +493,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, mAuthenticationStatsCollector), mBiometricContext, isStrongBiometric, - mUsageStats, mFaceSensors.get(sensorId).getLockoutCache(), + mUsageStats, null /* lockoutTracker */, allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId)); scheduleForSensor(sensorId, client, new ClientMonitorCallback() { @Override @@ -619,7 +619,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mFaceSensors.get(sensorId).getLazySession(), token, callback, userId, mContext.getOpPackageName(), sensorId, BiometricLogger.ofUnknown(mContext), - mBiometricContext); + mBiometricContext, feature); scheduleForSensor(sensorId, client); }); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java index 0512017394af..079388822def 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java @@ -36,12 +36,12 @@ import java.util.function.Supplier; /** * Face-specific removal client for the {@link IFace} AIDL HAL interface. */ -class FaceRemovalClient extends RemovalClient<Face, AidlSession> { +public class FaceRemovalClient extends RemovalClient<Face, AidlSession> { private static final String TAG = "FaceRemovalClient"; final int[] mBiometricIds; - FaceRemovalClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, + public FaceRemovalClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int[] biometricIds, int userId, @NonNull String owner, @NonNull BiometricUtils<Face> utils, int sensorId, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java index 1a12fcdf5010..77b5592c5064 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java @@ -32,7 +32,6 @@ import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ErrorConsumer; import com.android.server.biometrics.sensors.HalClientMonitor; -import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; @@ -48,14 +47,14 @@ public class FaceResetLockoutClient extends HalClientMonitor<AidlSession> implem private static final String TAG = "FaceResetLockoutClient"; private final HardwareAuthToken mHardwareAuthToken; - private final LockoutCache mLockoutCache; + private final LockoutTracker mLockoutCache; private final LockoutResetDispatcher mLockoutResetDispatcher; private final int mBiometricStrength; - FaceResetLockoutClient(@NonNull Context context, + public FaceResetLockoutClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, - @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker, + @NonNull byte[] hardwareAuthToken, @NonNull LockoutTracker lockoutTracker, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @Authenticators.Types int biometricStrength) { super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner, @@ -107,7 +106,7 @@ public class FaceResetLockoutClient extends HalClientMonitor<AidlSession> implem * be used instead. */ static void resetLocalLockoutStateToNone(int sensorId, int userId, - @NonNull LockoutCache lockoutTracker, + @NonNull LockoutTracker lockoutTracker, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull AuthSessionCoordinator authSessionCoordinator, @Authenticators.Types int biometricStrength, long requestId) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java index 8838345de4d6..0d6143a7d0f0 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java @@ -38,7 +38,7 @@ public class FaceRevokeChallengeClient extends RevokeChallengeClient<AidlSession private final long mChallenge; - FaceRevokeChallengeClient(@NonNull Context context, + public FaceRevokeChallengeClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java index 6c143872ff8c..f6da8726564f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java @@ -46,7 +46,7 @@ public class FaceSetFeatureClient extends HalClientMonitor<AidlSession> implemen private final boolean mEnabled; private final HardwareAuthToken mHardwareAuthToken; - FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, + public FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index 2ad41c2a7a02..54e66eb4cca4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java @@ -23,16 +23,10 @@ import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.ITestSessionCallback; -import android.hardware.biometrics.face.AuthenticationFrame; -import android.hardware.biometrics.face.EnrollmentFrame; -import android.hardware.biometrics.face.Error; import android.hardware.biometrics.face.IFace; import android.hardware.biometrics.face.ISession; -import android.hardware.biometrics.face.ISessionCallback; -import android.hardware.face.Face; import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorPropertiesInternal; -import android.hardware.keymaster.HardwareAuthToken; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -44,29 +38,22 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; -import com.android.server.biometrics.HardwareAuthTokenUtils; import com.android.server.biometrics.SensorServiceStateProto; import com.android.server.biometrics.SensorStateProto; import com.android.server.biometrics.UserStateProto; import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; -import com.android.server.biometrics.sensors.AuthSessionCoordinator; -import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; -import com.android.server.biometrics.sensors.EnumerateConsumer; import com.android.server.biometrics.sensors.ErrorConsumer; import com.android.server.biometrics.sensors.LockoutCache; -import com.android.server.biometrics.sensors.LockoutConsumer; import com.android.server.biometrics.sensors.LockoutResetDispatcher; -import com.android.server.biometrics.sensors.RemovalConsumer; import com.android.server.biometrics.sensors.StartUserClient; import com.android.server.biometrics.sensors.StopUserClient; import com.android.server.biometrics.sensors.UserAwareBiometricScheduler; import com.android.server.biometrics.sensors.face.FaceUtils; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; @@ -91,397 +78,6 @@ public class Sensor { @NonNull private final Supplier<AidlSession> mLazySession; @Nullable AidlSession mCurrentSession; - @VisibleForTesting - public static class HalSessionCallback extends ISessionCallback.Stub { - /** - * Interface to sends results to the HalSessionCallback's owner. - */ - public interface Callback { - /** - * Invoked when the HAL sends ERROR_HW_UNAVAILABLE. - */ - void onHardwareUnavailable(); - } - - @NonNull - private final Context mContext; - @NonNull - private final Handler mHandler; - @NonNull - private final String mTag; - @NonNull - private final UserAwareBiometricScheduler mScheduler; - private final int mSensorId; - private final int mUserId; - @NonNull - private final LockoutCache mLockoutCache; - @NonNull - private final LockoutResetDispatcher mLockoutResetDispatcher; - - @NonNull - private AuthSessionCoordinator mAuthSessionCoordinator; - @NonNull - private final Callback mCallback; - - HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag, - @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId, - @NonNull LockoutCache lockoutTracker, - @NonNull LockoutResetDispatcher lockoutResetDispatcher, - @NonNull AuthSessionCoordinator authSessionCoordinator, - @NonNull Callback callback) { - mContext = context; - mHandler = handler; - mTag = tag; - mScheduler = scheduler; - mSensorId = sensorId; - mUserId = userId; - mLockoutCache = lockoutTracker; - mLockoutResetDispatcher = lockoutResetDispatcher; - mAuthSessionCoordinator = authSessionCoordinator; - mCallback = callback; - } - - @Override - public int getInterfaceVersion() { - return this.VERSION; - } - - @Override - public String getInterfaceHash() { - return this.HASH; - } - - @Override - public void onChallengeGenerated(long challenge) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceGenerateChallengeClient)) { - Slog.e(mTag, "onChallengeGenerated for wrong client: " - + Utils.getClientName(client)); - return; - } - - final FaceGenerateChallengeClient generateChallengeClient = - (FaceGenerateChallengeClient) client; - generateChallengeClient.onChallengeGenerated(mSensorId, mUserId, challenge); - }); - } - - @Override - public void onChallengeRevoked(long challenge) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceRevokeChallengeClient)) { - Slog.e(mTag, "onChallengeRevoked for wrong client: " - + Utils.getClientName(client)); - return; - } - - final FaceRevokeChallengeClient revokeChallengeClient = - (FaceRevokeChallengeClient) client; - revokeChallengeClient.onChallengeRevoked(mSensorId, mUserId, challenge); - }); - } - - @Override - public void onAuthenticationFrame(AuthenticationFrame frame) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceAuthenticationClient)) { - Slog.e(mTag, "onAuthenticationFrame for incompatible client: " - + Utils.getClientName(client)); - return; - - } - if (frame == null) { - Slog.e(mTag, "Received null authentication frame for client: " - + Utils.getClientName(client)); - return; - } - ((FaceAuthenticationClient) client).onAuthenticationFrame( - AidlConversionUtils.toFrameworkAuthenticationFrame(frame)); - }); - } - - @Override - public void onEnrollmentFrame(EnrollmentFrame frame) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceEnrollClient)) { - Slog.e(mTag, "onEnrollmentFrame for incompatible client: " - + Utils.getClientName(client)); - return; - } - if (frame == null) { - Slog.e(mTag, "Received null enrollment frame for client: " - + Utils.getClientName(client)); - return; - } - ((FaceEnrollClient) client).onEnrollmentFrame( - AidlConversionUtils.toFrameworkEnrollmentFrame(frame)); - }); - } - - @Override - public void onError(byte error, int vendorCode) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - Slog.d(mTag, "onError" - + ", client: " + Utils.getClientName(client) - + ", error: " + error - + ", vendorCode: " + vendorCode); - if (!(client instanceof ErrorConsumer)) { - Slog.e(mTag, "onError for non-error consumer: " - + Utils.getClientName(client)); - return; - } - - final ErrorConsumer errorConsumer = (ErrorConsumer) client; - errorConsumer.onError(AidlConversionUtils.toFrameworkError(error), vendorCode); - - if (error == Error.HW_UNAVAILABLE) { - mCallback.onHardwareUnavailable(); - } - }); - } - - @Override - public void onEnrollmentProgress(int enrollmentId, int remaining) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceEnrollClient)) { - Slog.e(mTag, "onEnrollmentProgress for non-enroll client: " - + Utils.getClientName(client)); - return; - } - - final int currentUserId = client.getTargetUserId(); - final CharSequence name = FaceUtils.getInstance(mSensorId) - .getUniqueName(mContext, currentUserId); - final Face face = new Face(name, enrollmentId, mSensorId); - - final FaceEnrollClient enrollClient = (FaceEnrollClient) client; - enrollClient.onEnrollResult(face, remaining); - }); - } - - @Override - public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof AuthenticationConsumer)) { - Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: " - + Utils.getClientName(client)); - return; - } - - final AuthenticationConsumer authenticationConsumer = - (AuthenticationConsumer) client; - final Face face = new Face("" /* name */, enrollmentId, mSensorId); - final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat); - final ArrayList<Byte> byteList = new ArrayList<>(); - for (byte b : byteArray) { - byteList.add(b); - } - authenticationConsumer.onAuthenticated(face, true /* authenticated */, byteList); - }); - } - - @Override - public void onAuthenticationFailed() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof AuthenticationConsumer)) { - Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: " - + Utils.getClientName(client)); - return; - } - - final AuthenticationConsumer authenticationConsumer = - (AuthenticationConsumer) client; - final Face face = new Face("" /* name */, 0 /* faceId */, mSensorId); - authenticationConsumer.onAuthenticated(face, false /* authenticated */, - null /* hat */); - }); - } - - @Override - public void onLockoutTimed(long durationMillis) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof LockoutConsumer)) { - Slog.e(mTag, "onLockoutTimed for non-lockout consumer: " - + Utils.getClientName(client)); - return; - } - - final LockoutConsumer lockoutConsumer = (LockoutConsumer) client; - lockoutConsumer.onLockoutTimed(durationMillis); - }); - } - - @Override - public void onLockoutPermanent() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof LockoutConsumer)) { - Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: " - + Utils.getClientName(client)); - return; - } - - final LockoutConsumer lockoutConsumer = (LockoutConsumer) client; - lockoutConsumer.onLockoutPermanent(); - }); - } - - @Override - public void onLockoutCleared() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceResetLockoutClient)) { - Slog.d(mTag, "onLockoutCleared outside of resetLockout by HAL"); - // Given that onLockoutCleared() can happen at any time, and is not necessarily - // coming from a specific client, set this to -1 to indicate it wasn't for a - // specific request. - FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId, - mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, - Utils.getCurrentStrength(mSensorId), -1 /* requestId */); - } else { - Slog.d(mTag, "onLockoutCleared after resetLockout"); - final FaceResetLockoutClient resetLockoutClient = - (FaceResetLockoutClient) client; - resetLockoutClient.onLockoutCleared(); - } - }); - } - - @Override - public void onInteractionDetected() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceDetectClient)) { - Slog.e(mTag, "onInteractionDetected for wrong client: " - + Utils.getClientName(client)); - return; - } - - final FaceDetectClient detectClient = (FaceDetectClient) client; - detectClient.onInteractionDetected(); - }); - } - - @Override - public void onEnrollmentsEnumerated(int[] enrollmentIds) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof EnumerateConsumer)) { - Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: " - + Utils.getClientName(client)); - return; - } - - final EnumerateConsumer enumerateConsumer = - (EnumerateConsumer) client; - if (enrollmentIds.length > 0) { - for (int i = 0; i < enrollmentIds.length; ++i) { - final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId); - enumerateConsumer.onEnumerationResult(face, enrollmentIds.length - i - 1); - } - } else { - enumerateConsumer.onEnumerationResult(null /* identifier */, 0 /* remaining */); - } - }); - } - - @Override - public void onFeaturesRetrieved(byte[] features) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceGetFeatureClient)) { - Slog.e(mTag, "onFeaturesRetrieved for non-get feature consumer: " - + Utils.getClientName(client)); - return; - } - final FaceGetFeatureClient faceGetFeatureClient = (FaceGetFeatureClient) client; - faceGetFeatureClient.onFeatureGet(true /* success */, features); - }); - - } - - @Override - public void onFeatureSet(byte feature) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceSetFeatureClient)) { - Slog.e(mTag, "onFeatureSet for non-set consumer: " - + Utils.getClientName(client)); - return; - } - - final FaceSetFeatureClient faceSetFeatureClient = (FaceSetFeatureClient) client; - faceSetFeatureClient.onFeatureSet(true /* success */); - }); - } - - @Override - public void onEnrollmentsRemoved(int[] enrollmentIds) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof RemovalConsumer)) { - Slog.e(mTag, "onRemoved for non-removal consumer: " - + Utils.getClientName(client)); - return; - } - - final RemovalConsumer removalConsumer = (RemovalConsumer) client; - if (enrollmentIds.length > 0) { - for (int i = 0; i < enrollmentIds.length; i++) { - final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId); - removalConsumer.onRemoved(face, enrollmentIds.length - i - 1); - } - } else { - removalConsumer.onRemoved(null /* identifier */, 0 /* remaining */); - } - }); - } - - @Override - public void onAuthenticatorIdRetrieved(long authenticatorId) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceGetAuthenticatorIdClient)) { - Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: " - + Utils.getClientName(client)); - return; - } - - final FaceGetAuthenticatorIdClient getAuthenticatorIdClient = - (FaceGetAuthenticatorIdClient) client; - getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId); - }); - } - - @Override - public void onAuthenticatorIdInvalidated(long newAuthenticatorId) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceInvalidationClient)) { - Slog.e(mTag, "onAuthenticatorIdInvalidated for wrong consumer: " - + Utils.getClientName(client)); - return; - } - - final FaceInvalidationClient invalidationClient = (FaceInvalidationClient) client; - invalidationClient.onAuthenticatorIdInvalidated(newAuthenticatorId); - }); - } - - @Override - public void onSessionClosed() { - mHandler.post(mScheduler::onUserStopped); - } - } Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context, @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties, @@ -511,9 +107,9 @@ public class Sensor { public StartUserClient<?, ?> getStartUserClient(int newUserId) { final int sensorId = mSensorProperties.sensorId; - final HalSessionCallback resultController = new HalSessionCallback(mContext, - mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache, - lockoutResetDispatcher, + final AidlResponseHandler resultController = new AidlResponseHandler( + mContext, mScheduler, sensorId, newUserId, + mLockoutCache, lockoutResetDispatcher, biometricContext.getAuthSessionCoordinator(), () -> { Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE"); mCurrentSession = null; diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java new file mode 100644 index 000000000000..eecf44b92918 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.hidl; + +import android.annotation.DurationMillisLong; +import android.annotation.NonNull; +import android.content.Context; +import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.common.OperationContext; +import android.hardware.biometrics.face.EnrollmentStageConfig; +import android.hardware.biometrics.face.ISession; +import android.hardware.biometrics.face.V1_0.IBiometricsFace; +import android.hardware.biometrics.face.V1_0.OptionalBool; +import android.hardware.biometrics.face.V1_0.Status; +import android.hardware.common.NativeHandle; +import android.hardware.face.Face; +import android.hardware.face.FaceManager; +import android.hardware.keymaster.HardwareAuthToken; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.face.aidl.AidlConversionUtils; +import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler; + +import java.time.Clock; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +/** + * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation. + */ +public class AidlToHidlAdapter implements ISession { + + private final String TAG = "AidlToHidlAdapter"; + private static final int CHALLENGE_TIMEOUT_SEC = 600; + @DurationMillisLong + private static final int GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS = 60 * 1000; + @DurationMillisLong + private static final int GENERATE_CHALLENGE_COUNTER_TTL_MILLIS = CHALLENGE_TIMEOUT_SEC * 1000; + private static final int INVALID_VALUE = -1; + private final Clock mClock; + private final List<Long> mGeneratedChallengeCount = new ArrayList<>(); + @VisibleForTesting static final int ENROLL_TIMEOUT_SEC = 75; + private long mGenerateChallengeCreatedAt = INVALID_VALUE; + private long mGenerateChallengeResult = INVALID_VALUE; + @NonNull private Supplier<IBiometricsFace> mSession; + private final int mUserId; + private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter; + private final Context mContext; + private int mFeature = INVALID_VALUE; + + public AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session, + int userId, AidlResponseHandler aidlResponseHandler) { + this(context, session, userId, aidlResponseHandler, Clock.systemUTC()); + } + + AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session, int userId, + AidlResponseHandler aidlResponseHandler, Clock clock) { + mSession = session; + mUserId = userId; + mContext = context; + mClock = clock; + setCallback(aidlResponseHandler); + } + + private void setCallback(AidlResponseHandler aidlResponseHandler) { + mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler); + try { + mSession.get().setCallback(mHidlToAidlCallbackConverter); + } catch (RemoteException e) { + Slog.d(TAG, "Failed to set callback"); + } + } + + @Override + public IBinder asBinder() { + return null; + } + + private boolean isGeneratedChallengeCacheValid() { + return mGenerateChallengeCreatedAt != INVALID_VALUE + && mGenerateChallengeResult != INVALID_VALUE + && mClock.millis() - mGenerateChallengeCreatedAt + < GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS; + } + + private void incrementChallengeCount() { + mGeneratedChallengeCount.add(0, mClock.millis()); + } + + private int decrementChallengeCount() { + final long now = mClock.millis(); + // ignore values that are old in case generate/revoke calls are not matched + // this doesn't ensure revoke if calls are mismatched but it keeps the list from growing + mGeneratedChallengeCount.removeIf(x -> now - x > GENERATE_CHALLENGE_COUNTER_TTL_MILLIS); + if (!mGeneratedChallengeCount.isEmpty()) { + mGeneratedChallengeCount.remove(0); + } + return mGeneratedChallengeCount.size(); + } + + @Override + public void generateChallenge() throws RemoteException { + incrementChallengeCount(); + if (isGeneratedChallengeCacheValid()) { + Slog.d(TAG, "Current challenge is cached and will be reused"); + mHidlToAidlCallbackConverter.onChallengeGenerated(mGenerateChallengeResult); + return; + } + mGenerateChallengeCreatedAt = mClock.millis(); + mGenerateChallengeResult = mSession.get().generateChallenge(CHALLENGE_TIMEOUT_SEC).value; + mHidlToAidlCallbackConverter.onChallengeGenerated(mGenerateChallengeResult); + } + + @Override + public void revokeChallenge(long challenge) throws RemoteException { + final boolean shouldRevoke = decrementChallengeCount() == 0; + if (!shouldRevoke) { + Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: " + + mGeneratedChallengeCount); + mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId, + BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */); + return; + } + mGenerateChallengeCreatedAt = INVALID_VALUE; + mGenerateChallengeResult = INVALID_VALUE; + mSession.get().revokeChallenge(); + mHidlToAidlCallbackConverter.onChallengeRevoked(0L); + } + + @Override + public EnrollmentStageConfig[] getEnrollmentConfig(byte enrollmentType) throws RemoteException { + //unsupported in HIDL + return null; + } + + @Override + public ICancellationSignal enroll(HardwareAuthToken hat, byte type, byte[] features, + NativeHandle previewSurface) throws RemoteException { + final ArrayList<Byte> token = new ArrayList<>(); + final byte[] hardwareAuthTokenArray = HardwareAuthTokenUtils.toByteArray(hat); + for (byte b : hardwareAuthTokenArray) { + token.add(b); + } + final ArrayList<Integer> disabledFeatures = new ArrayList<>(); + for (byte b: features) { + disabledFeatures.add(AidlConversionUtils.convertAidlToFrameworkFeature(b)); + } + mSession.get().enroll(token, ENROLL_TIMEOUT_SEC, disabledFeatures); + return new Cancellation(); + } + + @Override + public ICancellationSignal authenticate(long operationId) throws RemoteException { + mSession.get().authenticate(operationId); + return new Cancellation(); + } + + @Override + public ICancellationSignal detectInteraction() throws RemoteException { + mSession.get().authenticate(0); + return new Cancellation(); + } + + @Override + public void enumerateEnrollments() throws RemoteException { + mSession.get().enumerate(); + } + + @Override + public void removeEnrollments(int[] enrollmentIds) throws RemoteException { + mSession.get().remove(enrollmentIds[0]); + } + + /** + * Needs to be called before getFeatures is invoked. + */ + public void setFeature(int feature) { + mFeature = feature; + } + + @Override + public void getFeatures() throws RemoteException { + final int faceId = getFaceId(); + if (faceId == INVALID_VALUE || mFeature == INVALID_VALUE) { + return; + } + + final OptionalBool result = mSession.get() + .getFeature(mFeature, faceId); + + if (result.status == Status.OK && result.value) { + mHidlToAidlCallbackConverter.onFeatureGet(new byte[]{AidlConversionUtils + .convertFrameworkToAidlFeature(mFeature)}); + } else if (result.status == Status.OK) { + mHidlToAidlCallbackConverter.onFeatureGet(new byte[]{}); + } else { + mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId, + BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0 /* vendorCode */); + } + + mFeature = INVALID_VALUE; + } + + @Override + public void setFeature(HardwareAuthToken hat, byte feature, boolean enabled) + throws RemoteException { + final int faceId = getFaceId(); + if (faceId == INVALID_VALUE) { + return; + } + ArrayList<Byte> hardwareAuthTokenList = new ArrayList<>(); + for (byte b: HardwareAuthTokenUtils.toByteArray(hat)) { + hardwareAuthTokenList.add(b); + } + final int result = mSession.get().setFeature( + AidlConversionUtils.convertAidlToFrameworkFeature(feature), + enabled, hardwareAuthTokenList, faceId); + if (result == Status.OK) { + mHidlToAidlCallbackConverter.onFeatureSet(feature); + } else { + mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId, + BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0 /* vendorCode */); + } + } + + private int getFaceId() { + FaceManager faceManager = mContext.getSystemService(FaceManager.class); + List<Face> faces = faceManager.getEnrolledFaces(mUserId); + if (faces.isEmpty()) { + Slog.d(TAG, "No faces to get feature from."); + mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId, + BiometricFaceConstants.FACE_ERROR_NOT_ENROLLED, 0 /* vendorCode */); + return INVALID_VALUE; + } + + return faces.get(0).getBiometricId(); + } + + @Override + public void getAuthenticatorId() throws RemoteException { + long authenticatorId = mSession.get().getAuthenticatorId().value; + mHidlToAidlCallbackConverter.onAuthenticatorIdRetrieved(authenticatorId); + } + + @Override + public void invalidateAuthenticatorId() throws RemoteException { + //unsupported in HIDL + } + + @Override + public void resetLockout(HardwareAuthToken hat) throws RemoteException { + ArrayList<Byte> hardwareAuthToken = new ArrayList<>(); + for (byte b : HardwareAuthTokenUtils.toByteArray(hat)) { + hardwareAuthToken.add(b); + } + mSession.get().resetLockout(hardwareAuthToken); + } + + @Override + public void close() throws RemoteException { + //Unsupported in HIDL + } + + @Override + public ICancellationSignal authenticateWithContext(long operationId, OperationContext context) + throws RemoteException { + //Unsupported in HIDL + return null; + } + + @Override + public ICancellationSignal enrollWithContext(HardwareAuthToken hat, byte type, byte[] features, + NativeHandle previewSurface, OperationContext context) throws RemoteException { + //Unsupported in HIDL + return null; + } + + @Override + public ICancellationSignal detectInteractionWithContext(OperationContext context) + throws RemoteException { + //Unsupported in HIDL + return null; + } + + @Override + public void onContextChanged(OperationContext context) throws RemoteException { + //Unsupported in HIDL + } + + @Override + public int getInterfaceVersion() throws RemoteException { + //Unsupported in HIDL + return 0; + } + + @Override + public String getInterfaceHash() throws RemoteException { + //Unsupported in HIDL + return null; + } + + /** + * Cancellation in HIDL occurs for the entire session, instead of a specific client. + */ + private class Cancellation extends ICancellationSignal.Stub { + + Cancellation() {} + @Override + public void cancel() throws RemoteException { + try { + mSession.get().cancel(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when requesting cancel", e); + } + } + + @Override + public int getInterfaceVersion() throws RemoteException { + return 0; + } + + @Override + public String getInterfaceHash() throws RemoteException { + return null; + } + } +} 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 1499317478aa..46ce0b62e6d5 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 @@ -54,6 +54,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver; import com.android.server.biometrics.AuthenticationStatsCollector; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.SensorServiceStateProto; import com.android.server.biometrics.SensorStateProto; import com.android.server.biometrics.UserStateProto; @@ -61,6 +62,7 @@ import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AcquisitionClient; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; @@ -78,6 +80,8 @@ import com.android.server.biometrics.sensors.face.FaceUtils; import com.android.server.biometrics.sensors.face.LockoutHalImpl; import com.android.server.biometrics.sensors.face.ServiceProvider; import com.android.server.biometrics.sensors.face.UsageStats; +import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler; +import com.android.server.biometrics.sensors.face.aidl.AidlSession; import org.json.JSONArray; import org.json.JSONException; @@ -131,7 +135,9 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { private int mCurrentUserId = UserHandle.USER_NULL; private final int mSensorId; private final List<Long> mGeneratedChallengeCount = new ArrayList<>(); + private final LockoutResetDispatcher mLockoutResetDispatcher; private FaceGenerateChallengeClient mGeneratedChallengeCache = null; + private AidlSession mSession; private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() { @Override @@ -361,6 +367,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { mLockoutTracker = new LockoutHalImpl(); mHalResultController = new HalResultController(sensorProps.sensorId, context, mHandler, mScheduler, mLockoutTracker, lockoutResetDispatcher); + mLockoutResetDispatcher = lockoutResetDispatcher; mHalResultController.setCallback(() -> { mDaemon = null; mCurrentUserId = UserHandle.USER_NULL; @@ -420,6 +427,24 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { }); } + public int getCurrentUserId() { + return mCurrentUserId; + } + + synchronized AidlSession getSession() { + if (mDaemon != null && mSession != null) { + return mSession; + } else { + return mSession = new AidlSession(mContext, this::getDaemon, mCurrentUserId, + new AidlResponseHandler(mContext, mScheduler, mSensorId, + mCurrentUserId, mLockoutTracker, mLockoutResetDispatcher, + new AuthSessionCoordinator(), () -> { + mDaemon = null; + mCurrentUserId = UserHandle.USER_NULL; + })); + } + } + private synchronized IBiometricsFace getDaemon() { if (mTestHalEnabled) { final TestHal testHal = new TestHal(mContext, mSensorId); @@ -551,32 +576,63 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { mHandler.post(() -> { - incrementChallengeCount(); + scheduleUpdateActiveUserWithoutHandler(userId); - if (isGeneratedChallengeCacheValid()) { - Slog.d(TAG, "Current challenge is cached and will be reused"); - mGeneratedChallengeCache.reuseResult(receiver); - return; + if (Flags.deHidl()) { + scheduleGenerateChallengeAidl(userId, token, receiver, opPackageName); + } else { + scheduleGenerateChallengeHidl(userId, token, receiver, opPackageName); } + }); + } - scheduleUpdateActiveUserWithoutHandler(userId); + private void scheduleGenerateChallengeAidl(int userId, @NonNull IBinder token, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { + final com.android.server.biometrics.sensors.face.aidl.FaceGenerateChallengeClient client = + new com.android.server.biometrics.sensors.face.aidl.FaceGenerateChallengeClient( + mContext, this::getSession, token, + new ClientMonitorCallbackConverter(receiver), userId, opPackageName, + mSensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext); + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + if (client != clientMonitor) { + Slog.e(TAG, + "scheduleGenerateChallenge onClientStarted, mismatched client." + + " Expecting: " + client + ", received: " + + clientMonitor); + } + } + }); + } - final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext, - mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, - opPackageName, mSensorId, - createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, sSystemClock.millis()); - mGeneratedChallengeCache = client; - mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { - @Override - public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { - if (client != clientMonitor) { - Slog.e(TAG, "scheduleGenerateChallenge onClientStarted, mismatched client." - + " Expecting: " + client + ", received: " + clientMonitor); - } + private void scheduleGenerateChallengeHidl(int userId, @NonNull IBinder token, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { + incrementChallengeCount(); + if (isGeneratedChallengeCacheValid()) { + Slog.d(TAG, "Current challenge is cached and will be reused"); + mGeneratedChallengeCache.reuseResult(receiver); + return; + } + + final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext, + mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, + opPackageName, mSensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, sSystemClock.millis()); + mGeneratedChallengeCache = client; + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + if (client != clientMonitor) { + Slog.e(TAG, + "scheduleGenerateChallenge onClientStarted, mismatched client." + + " Expecting: " + client + ", received: " + + clientMonitor); } - }); + } }); } @@ -584,31 +640,62 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token, @NonNull String opPackageName, long challenge) { mHandler.post(() -> { - final boolean shouldRevoke = decrementChallengeCount() == 0; - if (!shouldRevoke) { - Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: " - + mGeneratedChallengeCount); - return; + if (Flags.deHidl()) { + scheduleRevokeChallengeAidl(userId, token, opPackageName); + } else { + scheduleRevokeChallengeHidl(userId, token, opPackageName); + } + }); + } + + private void scheduleRevokeChallengeAidl(int userId, @NonNull IBinder token, + @NonNull String opPackageName) { + final com.android.server.biometrics.sensors.face.aidl.FaceRevokeChallengeClient + client = + new com.android.server.biometrics.sensors.face.aidl.FaceRevokeChallengeClient( + mContext, this::getSession, token, userId, opPackageName, mSensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), mBiometricContext, 0L); + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + if (client != clientMonitor) { + Slog.e(TAG, + "scheduleRevokeChallenge, mismatched client." + "Expecting: " + + client + ", received: " + clientMonitor); + } } + }); + } - Slog.d(TAG, "scheduleRevokeChallenge executing - no active clients"); - mGeneratedChallengeCache = null; + private void scheduleRevokeChallengeHidl(int userId, @NonNull IBinder token, + @NonNull String opPackageName) { + final boolean shouldRevoke = decrementChallengeCount() == 0; + if (!shouldRevoke) { + Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: " + + mGeneratedChallengeCount); + return; + } - final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext, - mLazyDaemon, token, userId, opPackageName, mSensorId, - createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext); - mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { - @Override - public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, - boolean success) { - if (client != clientMonitor) { - Slog.e(TAG, "scheduleRevokeChallenge, mismatched client." - + "Expecting: " + client + ", received: " + clientMonitor); - } + Slog.d(TAG, "scheduleRevokeChallenge executing - no active clients"); + mGeneratedChallengeCache = null; + final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext, + mLazyDaemon, token, userId, opPackageName, mSensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext); + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + if (client != clientMonitor) { + Slog.e(TAG, + "scheduleRevokeChallenge, mismatched client." + "Expecting: " + + client + ", received: " + clientMonitor); } - }); + } }); } @@ -620,7 +707,62 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { final long id = mRequestCounter.incrementAndGet(); mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); + if (Flags.deHidl()) { + scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver, + opPackageName, disabledFeatures, previewSurface, id); + } else { + scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver, + opPackageName, disabledFeatures, previewSurface, id); + } + }); + return id; + } + + private void scheduleEnrollAidl(@NonNull IBinder token, + @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver, + @NonNull String opPackageName, @NonNull int[] disabledFeatures, + @Nullable Surface previewSurface, long id) { + final com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient client = + new com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient( + mContext, this::getSession, token, + new ClientMonitorCallbackConverter(receiver), userId, + hardwareAuthToken, opPackageName, id, + FaceUtils.getLegacyInstance(mSensorId), disabledFeatures, + ENROLL_TIMEOUT_SEC, previewSurface, mSensorId, + createLogger(BiometricsProtoEnums.ACTION_ENROLL, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), mBiometricContext, + mContext.getResources().getInteger( + com.android.internal.R.integer.config_faceMaxTemplatesPerUser), + false); + + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + mBiometricStateCallback.onClientStarted(clientMonitor); + } + + @Override + public void onBiometricAction(int action) { + mBiometricStateCallback.onBiometricAction(action); + } + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + mBiometricStateCallback.onClientFinished(clientMonitor, success); + if (success) { + // Update authenticatorIds + scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId()); + } + } + }); + } + + private void scheduleEnrollHidl(@NonNull IBinder token, + @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver, + @NonNull String opPackageName, @NonNull int[] disabledFeatures, + @Nullable Surface previewSurface, long id) { final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures, @@ -628,7 +770,6 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { createLogger(BiometricsProtoEnums.ACTION_ENROLL, BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), mBiometricContext); - mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { @@ -650,8 +791,6 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { } } }); - }); - return id; } @Override @@ -683,18 +822,46 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { scheduleUpdateActiveUserWithoutHandler(userId); final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId); - final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext, - mLazyDaemon, token, requestId, receiver, operationId, restricted, - options, cookie, false /* requireConfirmation */, - createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, - mAuthenticationStatsCollector), - mBiometricContext, isStrongBiometric, mLockoutTracker, - mUsageStats, allowBackgroundAuthentication, - Utils.getCurrentStrength(mSensorId)); - mScheduler.scheduleClientMonitor(client); + if (Flags.deHidl()) { + scheduleAuthenticateAidl(token, operationId, cookie, receiver, options, requestId, + restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric); + } else { + scheduleAuthenticateHidl(token, operationId, cookie, receiver, options, requestId, + restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric); + } }); } + private void scheduleAuthenticateAidl(@NonNull IBinder token, long operationId, + int cookie, @NonNull ClientMonitorCallbackConverter receiver, + @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted, + int statsClient, boolean allowBackgroundAuthentication, boolean isStrongBiometric) { + final com.android.server.biometrics.sensors.face.aidl.FaceAuthenticationClient + client = + new com.android.server.biometrics.sensors.face.aidl.FaceAuthenticationClient( + mContext, this::getSession, token, requestId, receiver, operationId, + restricted, options, cookie, false /* requireConfirmation */, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, + mAuthenticationStatsCollector), mBiometricContext, + isStrongBiometric, mUsageStats, mLockoutTracker, + allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId)); + mScheduler.scheduleClientMonitor(client); + } + + private void scheduleAuthenticateHidl(@NonNull IBinder token, long operationId, + int cookie, @NonNull ClientMonitorCallbackConverter receiver, + @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted, + int statsClient, boolean allowBackgroundAuthentication, boolean isStrongBiometric) { + final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext, + mLazyDaemon, token, requestId, receiver, operationId, restricted, options, + cookie, false /* requireConfirmation */, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, + mAuthenticationStatsCollector), mBiometricContext, + isStrongBiometric, mLockoutTracker, mUsageStats, + allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId)); + mScheduler.scheduleClientMonitor(client); + } + @Override public long scheduleAuthenticate(@NonNull IBinder token, long operationId, int cookie, @NonNull ClientMonitorCallbackConverter receiver, @@ -719,13 +886,11 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); - final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token, - new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName, - FaceUtils.getLegacyInstance(mSensorId), mSensorId, - createLogger(BiometricsProtoEnums.ACTION_REMOVE, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, mAuthenticatorIds); - mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + if (Flags.deHidl()) { + scheduleRemoveAidl(token, userId, receiver, opPackageName, faceId); + } else { + scheduleRemoveHidl(token, userId, receiver, opPackageName, faceId); + } }); } @@ -736,17 +901,39 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { scheduleUpdateActiveUserWithoutHandler(userId); // For IBiometricsFace@1.0, remove(0) means remove all enrollments - final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token, - new ClientMonitorCallbackConverter(receiver), 0 /* faceId */, userId, - opPackageName, - FaceUtils.getLegacyInstance(mSensorId), mSensorId, - createLogger(BiometricsProtoEnums.ACTION_REMOVE, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, mAuthenticatorIds); - mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + if (Flags.deHidl()) { + scheduleRemoveAidl(token, userId, receiver, opPackageName, 0); + } else { + scheduleRemoveHidl(token, userId, receiver, opPackageName, 0); + } }); } + private void scheduleRemoveAidl(@NonNull IBinder token, int userId, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, int faceId) { + final com.android.server.biometrics.sensors.face.aidl.FaceRemovalClient client = + new com.android.server.biometrics.sensors.face.aidl.FaceRemovalClient( + mContext, this::getSession, token, + new ClientMonitorCallbackConverter(receiver), new int[]{faceId}, userId, + opPackageName, FaceUtils.getLegacyInstance(mSensorId), mSensorId, + createLogger(BiometricsProtoEnums.ACTION_REMOVE, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), mBiometricContext, + mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + + private void scheduleRemoveHidl(@NonNull IBinder token, int userId, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, int faceId) { + final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token, + new ClientMonitorCallbackConverter(receiver), faceId, userId, + opPackageName, FaceUtils.getLegacyInstance(mSensorId), mSensorId, + createLogger(BiometricsProtoEnums.ACTION_REMOVE, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + @Override public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) { mHandler.post(() -> { @@ -756,89 +943,180 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { } scheduleUpdateActiveUserWithoutHandler(userId); - - final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext, - mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, - createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, hardwareAuthToken); - mScheduler.scheduleClientMonitor(client); + if (Flags.deHidl()) { + scheduleResetLockoutAidl(userId, hardwareAuthToken); + } else { + scheduleResetLockoutHidl(userId, hardwareAuthToken); + } }); } + private void scheduleResetLockoutAidl(int userId, + @NonNull byte[] hardwareAuthToken) { + final com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient client = + new com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient( + mContext, this::getSession, userId, mContext.getOpPackageName(), + mSensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext, hardwareAuthToken, mLockoutTracker, + mLockoutResetDispatcher, mSensorProperties.sensorStrength); + mScheduler.scheduleClientMonitor(client); + } + + private void scheduleResetLockoutHidl(int userId, + @NonNull byte[] hardwareAuthToken) { + final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext, + mLazyDaemon, + userId, mContext.getOpPackageName(), mSensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, hardwareAuthToken); + mScheduler.scheduleClientMonitor(client); + } + @Override public void scheduleSetFeature(int sensorId, @NonNull IBinder token, int userId, int feature, boolean enabled, @NonNull byte[] hardwareAuthToken, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { mHandler.post(() -> { - final List<Face> faces = getEnrolledFaces(sensorId, userId); - if (faces.isEmpty()) { - Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId); - return; + scheduleUpdateActiveUserWithoutHandler(userId); + if (Flags.deHidl()) { + scheduleSetFeatureAidl(sensorId, token, userId, feature, enabled, hardwareAuthToken, + receiver, opPackageName); + } else { + scheduleSetFeatureHidl(sensorId, token, userId, feature, enabled, hardwareAuthToken, + receiver, opPackageName); } + }); + } - scheduleUpdateActiveUserWithoutHandler(userId); + private void scheduleSetFeatureHidl(int sensorId, @NonNull IBinder token, int userId, + int feature, boolean enabled, @NonNull byte[] hardwareAuthToken, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { + final List<Face> faces = getEnrolledFaces(sensorId, userId); + if (faces.isEmpty()) { + Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId); + return; + } + final int faceId = faces.get(0).getBiometricId(); + final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext, mLazyDaemon, + token, new ClientMonitorCallbackConverter(receiver), userId, opPackageName, + mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, feature, + enabled, hardwareAuthToken, faceId); + mScheduler.scheduleClientMonitor(client); + } - final int faceId = faces.get(0).getBiometricId(); - final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext, - mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, - opPackageName, mSensorId, BiometricLogger.ofUnknown(mContext), - mBiometricContext, - feature, enabled, hardwareAuthToken, faceId); - mScheduler.scheduleClientMonitor(client); - }); + private void scheduleSetFeatureAidl(int sensorId, @NonNull IBinder token, int userId, + int feature, boolean enabled, @NonNull byte[] hardwareAuthToken, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { + final com.android.server.biometrics.sensors.face.aidl.FaceSetFeatureClient client = + new com.android.server.biometrics.sensors.face.aidl.FaceSetFeatureClient( + mContext, this::getSession, token, + new ClientMonitorCallbackConverter(receiver), userId, opPackageName, + mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, + feature, enabled, hardwareAuthToken); + mScheduler.scheduleClientMonitor(client); } + @Override public void scheduleGetFeature(int sensorId, @NonNull IBinder token, int userId, int feature, @Nullable ClientMonitorCallbackConverter listener, @NonNull String opPackageName) { mHandler.post(() -> { - final List<Face> faces = getEnrolledFaces(sensorId, userId); - if (faces.isEmpty()) { - Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId); - return; + scheduleUpdateActiveUserWithoutHandler(userId); + + if (Flags.deHidl()) { + scheduleGetFeatureAidl(token, userId, feature, listener, + opPackageName); + } else { + scheduleGetFeatureHidl(sensorId, token, userId, feature, listener, + opPackageName); } + }); + } - scheduleUpdateActiveUserWithoutHandler(userId); + private void scheduleGetFeatureHidl(int sensorId, @NonNull IBinder token, int userId, + int feature, @Nullable ClientMonitorCallbackConverter listener, + @NonNull String opPackageName) { + final List<Face> faces = getEnrolledFaces(sensorId, userId); + if (faces.isEmpty()) { + Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId); + return; + } - final int faceId = faces.get(0).getBiometricId(); - final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon, - token, listener, userId, opPackageName, mSensorId, - BiometricLogger.ofUnknown(mContext), mBiometricContext, - feature, faceId); - mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { - @Override - public void onClientFinished( - @NonNull BaseClientMonitor clientMonitor, boolean success) { - if (success && feature == BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION) { - final int settingsValue = client.getValue() ? 1 : 0; - Slog.d(TAG, "Updating attention value for user: " + userId - + " to value: " + settingsValue); - Settings.Secure.putIntForUser(mContext.getContentResolver(), - Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED, - settingsValue, userId); - } + final int faceId = faces.get(0).getBiometricId(); + final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon, + token, listener, userId, opPackageName, mSensorId, + BiometricLogger.ofUnknown(mContext), mBiometricContext, feature, faceId); + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + if (success + && feature == BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION) { + final int settingsValue = client.getValue() ? 1 : 0; + Slog.d(TAG, + "Updating attention value for user: " + userId + " to value: " + + settingsValue); + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED, settingsValue, + userId); } - }); + } }); } + private void scheduleGetFeatureAidl(@NonNull IBinder token, int userId, + int feature, @Nullable ClientMonitorCallbackConverter listener, + @NonNull String opPackageName) { + final com.android.server.biometrics.sensors.face.aidl.FaceGetFeatureClient client = + new com.android.server.biometrics.sensors.face.aidl.FaceGetFeatureClient( + mContext, this::getSession, token, listener, userId, opPackageName, + mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, + feature); + mScheduler.scheduleClientMonitor(client); + } + private void scheduleInternalCleanup(int userId, @Nullable ClientMonitorCallback callback) { mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); - - final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext, - mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, - createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, - FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); - mScheduler.scheduleClientMonitor(client, new ClientMonitorCompositeCallback(callback, - mBiometricStateCallback)); + if (Flags.deHidl()) { + scheduleInternalCleanupAidl(userId, callback); + } else { + scheduleInternalCleanupHidl(userId, callback); + } }); } + private void scheduleInternalCleanupHidl(int userId, + @Nullable ClientMonitorCallback callback) { + final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext, + mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, + createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, FaceUtils.getLegacyInstance(mSensorId), + mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, + new ClientMonitorCompositeCallback(callback, mBiometricStateCallback)); + } + + private void scheduleInternalCleanupAidl(int userId, + @Nullable ClientMonitorCallback callback) { + final com.android.server.biometrics.sensors.face.aidl.FaceInternalCleanupClient + client = + new com.android.server.biometrics.sensors.face.aidl.FaceInternalCleanupClient( + mContext, this::getSession, userId, mContext.getOpPackageName(), + mSensorId, createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, FaceUtils.getLegacyInstance(mSensorId), + mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, + new ClientMonitorCompositeCallback(callback, mBiometricStateCallback)); + } + @Override public void scheduleInternalCleanup(int sensorId, int userId, @Nullable ClientMonitorCallback callback) { @@ -970,6 +1248,10 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { if (success) { + if (mCurrentUserId != targetUserId) { + // Create new session with updated user ID + mSession = null; + } 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/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java new file mode 100644 index 000000000000..36a9790d2d4b --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.hidl; + +import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback; + +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler; + +import java.util.ArrayList; + +/** + * Convert HIDL-specific callback interface {@link IBiometricsFaceClientCallback} to AIDL + * response handler. + */ +public class HidlToAidlCallbackConverter extends IBiometricsFaceClientCallback.Stub { + + private final AidlResponseHandler mAidlResponseHandler; + + public HidlToAidlCallbackConverter(AidlResponseHandler aidlResponseHandler) { + mAidlResponseHandler = aidlResponseHandler; + } + + @Override + public void onEnrollResult( + long deviceId, int faceId, int userId, int remaining) { + mAidlResponseHandler.onEnrollmentProgress(faceId, remaining); + } + + @Override + public void onAuthenticated(long deviceId, int faceId, int userId, + ArrayList<Byte> token) { + final boolean authenticated = faceId != 0; + byte[] hardwareAuthToken = new byte[token.size()]; + + for (int i = 0; i < token.size(); i++) { + hardwareAuthToken[i] = token.get(i); + } + + if (authenticated) { + mAidlResponseHandler.onAuthenticationSucceeded(faceId, + HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken)); + } else { + mAidlResponseHandler.onAuthenticationFailed(); + } + } + + @Override + public void onAcquired(long deviceId, int userId, int acquiredInfo, + int vendorCode) { + mAidlResponseHandler.onAcquired(acquiredInfo, vendorCode); + } + + @Override + public void onError(long deviceId, int userId, int error, int vendorCode) { + mAidlResponseHandler.onError(error, vendorCode); + } + + @Override + public void onRemoved(long deviceId, ArrayList<Integer> removed, int userId) { + int[] enrollmentIds = new int[removed.size()]; + for (int i = 0; i < removed.size(); i++) { + enrollmentIds[i] = removed.get(i); + } + mAidlResponseHandler.onEnrollmentsRemoved(enrollmentIds); + } + + @Override + public void onEnumerate(long deviceId, ArrayList<Integer> faceIds, int userId) { + int[] enrollmentIds = new int[faceIds.size()]; + for (int i = 0; i < faceIds.size(); i++) { + enrollmentIds[i] = faceIds.get(i); + } + mAidlResponseHandler.onEnrollmentsEnumerated(enrollmentIds); + } + + @Override + public void onLockoutChanged(long duration) { + mAidlResponseHandler.onLockoutChanged(duration); + } + + void onChallengeGenerated(long challenge) { + mAidlResponseHandler.onChallengeGenerated(challenge); + } + + void onChallengeRevoked(long challenge) { + mAidlResponseHandler.onChallengeRevoked(challenge); + } + + void onFeatureGet(byte[] features) { + mAidlResponseHandler.onFeaturesRetrieved(features); + } + + void onFeatureSet(byte feature) { + mAidlResponseHandler.onFeatureSet(feature); + } + + void onAuthenticatorIdRetrieved(long authenticatorId) { + mAidlResponseHandler.onAuthenticatorIdRetrieved(authenticatorId); + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java new file mode 100644 index 000000000000..4a019436cf6f --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.biometrics.face.Error; +import android.hardware.biometrics.fingerprint.ISessionCallback; +import android.hardware.fingerprint.Fingerprint; +import android.hardware.keymaster.HardwareAuthToken; +import android.util.Slog; + +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.Utils; +import com.android.server.biometrics.sensors.AcquisitionClient; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.AuthenticationConsumer; +import com.android.server.biometrics.sensors.BaseClientMonitor; +import com.android.server.biometrics.sensors.BiometricScheduler; +import com.android.server.biometrics.sensors.EnumerateConsumer; +import com.android.server.biometrics.sensors.ErrorConsumer; +import com.android.server.biometrics.sensors.LockoutCache; +import com.android.server.biometrics.sensors.LockoutConsumer; +import com.android.server.biometrics.sensors.LockoutResetDispatcher; +import com.android.server.biometrics.sensors.RemovalConsumer; +import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; + +import java.util.ArrayList; +import java.util.function.Consumer; + + +/** + * Response handler for the {@link ISessionCallback} HAL AIDL interface. + */ +public class AidlResponseHandler extends ISessionCallback.Stub { + + /** + * Interface to send results to the AidlResponseHandler's owner. + */ + public interface HardwareUnavailableCallback { + /** + * Invoked when the HAL sends ERROR_HW_UNAVAILABLE. + */ + void onHardwareUnavailable(); + } + + private static final String TAG = "AidlResponseHandler"; + + @NonNull + private final Context mContext; + @NonNull + private final BiometricScheduler mScheduler; + private final int mSensorId; + private final int mUserId; + @NonNull + private final LockoutCache mLockoutCache; + @NonNull + private final LockoutResetDispatcher mLockoutResetDispatcher; + @NonNull + private final AuthSessionCoordinator mAuthSessionCoordinator; + @NonNull + private final HardwareUnavailableCallback mHardwareUnavailableCallback; + + public AidlResponseHandler(@NonNull Context context, + @NonNull BiometricScheduler scheduler, int sensorId, int userId, + @NonNull LockoutCache lockoutTracker, + @NonNull LockoutResetDispatcher lockoutResetDispatcher, + @NonNull AuthSessionCoordinator authSessionCoordinator, + @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) { + mContext = context; + mScheduler = scheduler; + mSensorId = sensorId; + mUserId = userId; + mLockoutCache = lockoutTracker; + mLockoutResetDispatcher = lockoutResetDispatcher; + mAuthSessionCoordinator = authSessionCoordinator; + mHardwareUnavailableCallback = hardwareUnavailableCallback; + } + + @Override + public int getInterfaceVersion() { + return this.VERSION; + } + + @Override + public String getInterfaceHash() { + return this.HASH; + } + + @Override + public void onChallengeGenerated(long challenge) { + handleResponse(FingerprintGenerateChallengeClient.class, (c) -> c.onChallengeGenerated( + mSensorId, mUserId, challenge), null); + } + + @Override + public void onChallengeRevoked(long challenge) { + handleResponse(FingerprintRevokeChallengeClient.class, (c) -> c.onChallengeRevoked( + challenge), null); + } + + /** + * Handles acquired messages sent by the HAL (specifically for HIDL HAL). + */ + public void onAcquired(int acquiredInfo, int vendorCode) { + handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode), + null); + } + + @Override + public void onAcquired(byte info, int vendorCode) { + handleResponse(AcquisitionClient.class, (c) -> c.onAcquired( + AidlConversionUtils.toFrameworkAcquiredInfo(info), vendorCode), null); + } + + /** + * Handle error messages from the HAL. + */ + public void onError(int error, int vendorCode) { + handleResponse(ErrorConsumer.class, (c) -> { + c.onError(error, vendorCode); + if (error == Error.HW_UNAVAILABLE) { + mHardwareUnavailableCallback.onHardwareUnavailable(); + } + }, null); + } + + @Override + public void onError(byte error, int vendorCode) { + onError(AidlConversionUtils.toFrameworkError(error), vendorCode); + } + + @Override + public void onEnrollmentProgress(int enrollmentId, int remaining) { + BaseClientMonitor client = mScheduler.getCurrentClient(); + final int currentUserId; + if (client == null) { + return; + } else { + currentUserId = client.getTargetUserId(); + } + final CharSequence name = FingerprintUtils.getInstance(mSensorId) + .getUniqueName(mContext, currentUserId); + final Fingerprint fingerprint = new Fingerprint(name, currentUserId, + enrollmentId, mSensorId); + handleResponse(FingerprintEnrollClient.class, (c) -> c.onEnrollResult(fingerprint, + remaining), null); + } + + @Override + public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) { + final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId); + final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat); + final ArrayList<Byte> byteList = new ArrayList<>(); + for (byte b : byteArray) { + byteList.add(b); + } + handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(fp, + true /* authenticated */, byteList), (c) -> onInteractionDetected()); + } + + @Override + public void onAuthenticationFailed() { + final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, mSensorId); + handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(fp, + false /* authenticated */, null /* hardwareAuthToken */), + (c) -> onInteractionDetected()); + } + + @Override + public void onLockoutTimed(long durationMillis) { + handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis), + null); + } + + @Override + public void onLockoutPermanent() { + handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null); + } + + @Override + public void onLockoutCleared() { + handleResponse(FingerprintResetLockoutClient.class, + FingerprintResetLockoutClient::onLockoutCleared, + (c) -> FingerprintResetLockoutClient.resetLocalLockoutStateToNone( + mSensorId, mUserId, mLockoutCache, mLockoutResetDispatcher, + mAuthSessionCoordinator, Utils.getCurrentStrength(mSensorId), + -1 /* requestId */)); + } + + @Override + public void onInteractionDetected() { + handleResponse(FingerprintDetectClient.class, + FingerprintDetectClient::onInteractionDetected, null); + } + + @Override + public void onEnrollmentsEnumerated(int[] enrollmentIds) { + if (enrollmentIds.length > 0) { + for (int i = 0; i < enrollmentIds.length; i++) { + final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId); + int finalI = i; + handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(fp, + enrollmentIds.length - finalI - 1), null); + } + } else { + handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(null, + 0), null); + } + } + + @Override + public void onEnrollmentsRemoved(int[] enrollmentIds) { + if (enrollmentIds.length > 0) { + for (int i = 0; i < enrollmentIds.length; i++) { + final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId); + int finalI = i; + handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(fp, + enrollmentIds.length - finalI - 1), null); + } + } else { + handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null, 0), + null); + } + } + + @Override + public void onAuthenticatorIdRetrieved(long authenticatorId) { + handleResponse(FingerprintGetAuthenticatorIdClient.class, + (c) -> c.onAuthenticatorIdRetrieved(authenticatorId), null); + } + + @Override + public void onAuthenticatorIdInvalidated(long newAuthenticatorId) { + handleResponse(FingerprintInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated( + newAuthenticatorId), null); + } + + private <T> void handleResponse(@NonNull Class<T> className, + @NonNull Consumer<T> action, + @Nullable Consumer<BaseClientMonitor> alternateAction) { + mScheduler.getHandler().post(() -> { + final BaseClientMonitor client = mScheduler.getCurrentClient(); + if (className.isInstance(client)) { + action.accept((T) client); + } else { + Slog.e(TAG, "Client monitor is not an instance of " + className.getName()); + if (alternateAction != null) { + alternateAction.accept(client); + } + } + }); + } + + @Override + public void onSessionClosed() { + mScheduler.getHandler().post(mScheduler::onUserStopped); + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java index 55861bb4cc6f..299a310caee9 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java @@ -16,10 +16,13 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; -import static com.android.server.biometrics.sensors.fingerprint.aidl.Sensor.HalSessionCallback; - import android.annotation.NonNull; import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; + +import com.android.server.biometrics.sensors.fingerprint.hidl.AidlToHidlAdapter; + +import java.util.function.Supplier; /** * A holder for an AIDL {@link ISession} with additional metadata about the current user @@ -30,14 +33,22 @@ public class AidlSession { private final int mHalInterfaceVersion; @NonNull private final ISession mSession; private final int mUserId; - @NonNull private final HalSessionCallback mHalSessionCallback; + @NonNull private final AidlResponseHandler mAidlResponseHandler; public AidlSession(int halInterfaceVersion, @NonNull ISession session, int userId, - HalSessionCallback halSessionCallback) { + AidlResponseHandler aidlResponseHandler) { mHalInterfaceVersion = halInterfaceVersion; mSession = session; mUserId = userId; - mHalSessionCallback = halSessionCallback; + mAidlResponseHandler = aidlResponseHandler; + } + + public AidlSession(@NonNull Supplier<IBiometricsFingerprint> session, + int userId, AidlResponseHandler aidlResponseHandler) { + mSession = new AidlToHidlAdapter(session, userId, aidlResponseHandler); + mHalInterfaceVersion = 0; + mUserId = userId; + mAidlResponseHandler = aidlResponseHandler; } /** The underlying {@link ISession}. */ @@ -51,8 +62,8 @@ public class AidlSession { } /** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */ - HalSessionCallback getHalSessionCallback() { - return mHalSessionCallback; + AidlResponseHandler getHalSessionCallback() { + return mAidlResponseHandler; } /** diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index 54d1faa39be0..337c3c299e54 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.app.TaskStackListener; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired; import android.hardware.biometrics.BiometricManager.Authenticators; @@ -51,8 +52,8 @@ import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; -import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutConsumer; +import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.PerformanceTracker; import com.android.server.biometrics.sensors.SensorOverlays; import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler; @@ -66,7 +67,7 @@ import java.util.function.Supplier; * Fingerprint-specific authentication client supporting the {@link * android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface. */ -class FingerprintAuthenticationClient +public class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession, FingerprintAuthenticateOptions> implements Udfps, LockoutConsumer, PowerPressHandler { private static final String TAG = "FingerprintAuthenticationClient"; @@ -93,7 +94,7 @@ class FingerprintAuthenticationClient private Runnable mAuthSuccessRunnable; private final Clock mClock; - FingerprintAuthenticationClient( + public FingerprintAuthenticationClient( @NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @@ -108,14 +109,14 @@ class FingerprintAuthenticationClient @NonNull BiometricContext biometricContext, boolean isStrongBiometric, @Nullable TaskStackListener taskStackListener, - @NonNull LockoutCache lockoutCache, @Nullable IUdfpsOverlayController udfpsOverlayController, @Nullable ISidefpsController sidefpsController, boolean allowBackgroundAuthentication, @NonNull FingerprintSensorPropertiesInternal sensorProps, @NonNull Handler handler, @Authenticators.Types int biometricStrength, - @NonNull Clock clock) { + @NonNull Clock clock, + @Nullable LockoutTracker lockoutTracker) { super( context, lazyDaemon, @@ -130,7 +131,7 @@ class FingerprintAuthenticationClient biometricContext, isStrongBiometric, taskStackListener, - null /* lockoutCache */, + lockoutTracker, allowBackgroundAuthentication, false /* shouldVibrate */, biometricStrength); @@ -211,6 +212,7 @@ class FingerprintAuthenticationClient boolean authenticated, ArrayList<Byte> token) { super.onAuthenticated(identifier, authenticated, token); + handleLockout(authenticated); if (authenticated) { mState = STATE_STOPPED; mSensorOverlays.hide(getSensorId()); @@ -219,6 +221,32 @@ class FingerprintAuthenticationClient } } + private void handleLockout(boolean authenticated) { + if (getLockoutTracker() == null) { + Slog.d(TAG, "Lockout is implemented by the HAL"); + return; + } + if (authenticated) { + getLockoutTracker().resetFailedAttemptsForUser(true /* clearAttemptCounter */, + getTargetUserId()); + } else { + @LockoutTracker.LockoutMode final int lockoutMode = + getLockoutTracker().getLockoutModeForUser(getTargetUserId()); + if (lockoutMode != LockoutTracker.LOCKOUT_NONE) { + Slog.w(TAG, "Fingerprint locked out, lockoutMode(" + lockoutMode + ")"); + final int errorCode = lockoutMode == LockoutTracker.LOCKOUT_TIMED + ? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT + : BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT; + // Send the error, but do not invoke the FinishCallback yet. Since lockout is not + // controlled by the HAL, the framework must stop the sensor before finishing the + // client. + mSensorOverlays.hide(getSensorId()); + onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */); + cancel(); + } + } + } + @Override public void onAcquired(@FingerprintAcquired int acquiredInfo, int vendorCode) { // For UDFPS, notify SysUI with acquiredInfo, so that the illumination can be turned off diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java index 4502e5d0c4b6..e2413ee1c016 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java @@ -43,7 +43,8 @@ import java.util.function.Supplier; * Performs fingerprint detection without exposing any matching information (e.g. accept/reject * have the same haptic, lockout counter is not increased). */ -class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements DetectionConsumer { +public class FingerprintDetectClient extends AcquisitionClient<AidlSession> + implements DetectionConsumer { private static final String TAG = "FingerprintDetectClient"; @@ -52,7 +53,8 @@ class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements @NonNull private final SensorOverlays mSensorOverlays; @Nullable private ICancellationSignal mCancellationSignal; - FingerprintDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, + public FingerprintDetectClient(@NonNull Context context, + @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId, @NonNull ClientMonitorCallbackConverter listener, @NonNull FingerprintAuthenticateOptions options, 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 46ff6b4fab1a..06550d8b4fce 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 @@ -49,14 +49,13 @@ import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; import com.android.server.biometrics.sensors.EnrollClient; import com.android.server.biometrics.sensors.SensorOverlays; -import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler; import com.android.server.biometrics.sensors.fingerprint.Udfps; import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper; import java.util.function.Supplier; -class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps, +public class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps, PowerPressHandler { private static final String TAG = "FingerprintEnrollClient"; @@ -72,12 +71,16 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps private static boolean shouldVibrateFor(Context context, FingerprintSensorPropertiesInternal sensorProps) { - final AccessibilityManager am = context.getSystemService(AccessibilityManager.class); - final boolean isAccessbilityEnabled = am.isTouchExplorationEnabled(); - return !sensorProps.isAnyUdfpsType() || isAccessbilityEnabled; + if (sensorProps != null) { + final AccessibilityManager am = context.getSystemService(AccessibilityManager.class); + final boolean isAccessbilityEnabled = am.isTouchExplorationEnabled(); + return !sensorProps.isAnyUdfpsType() || isAccessbilityEnabled; + } else { + return true; + } } - FingerprintEnrollClient(@NonNull Context context, + public FingerprintEnrollClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String owner, @@ -89,8 +92,8 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) { // UDFPS haptics occur when an image is acquired (instead of when the result is known) super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, - 0 /* timeoutSec */, sensorId, shouldVibrateFor(context, sensorProps), logger, - biometricContext); + 0 /* timeoutSec */, sensorId, shouldVibrateFor(context, sensorProps), + logger, biometricContext); setRequestId(requestId); mSensorProps = sensorProps; mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); @@ -136,7 +139,7 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD; // For UDFPS, notify SysUI that the illumination can be turned off. // See AcquiredInfo#GOOD and AcquiredInfo#RETRYING_CAPTURE - if (mSensorProps.isAnyUdfpsType()) { + if (mSensorProps != null && mSensorProps.isAnyUdfpsType()) { if (acquiredGood && mShouldVibrate) { vibrateSuccess(); } @@ -162,8 +165,7 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps @Override protected boolean hasReachedEnrollmentLimit() { - return FingerprintUtils.getInstance(getSensorId()) - .getBiometricsForUser(getContext(), getTargetUserId()).size() + return mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).size() >= mMaxTemplatesPerUser; } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java index ddae8bedf7c1..ce693ff58e19 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java @@ -33,10 +33,10 @@ import java.util.function.Supplier; /** * Fingerprint-specific generateChallenge client for the {@link IFingerprint} AIDL HAL interface. */ -class FingerprintGenerateChallengeClient extends GenerateChallengeClient<AidlSession> { +public class FingerprintGenerateChallengeClient extends GenerateChallengeClient<AidlSession> { private static final String TAG = "FingerprintGenerateChallengeClient"; - FingerprintGenerateChallengeClient(@NonNull Context context, + public FingerprintGenerateChallengeClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java index ff9127f516af..5edc2ca080ad 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java @@ -39,9 +39,10 @@ import java.util.function.Supplier; * Fingerprint-specific internal cleanup client supporting the * {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface. */ -class FingerprintInternalCleanupClient extends InternalCleanupClient<Fingerprint, AidlSession> { +public class FingerprintInternalCleanupClient + extends InternalCleanupClient<Fingerprint, AidlSession> { - FingerprintInternalCleanupClient(@NonNull Context context, + public FingerprintInternalCleanupClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, 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 e42b66472cbe..9985b06833c9 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 @@ -428,8 +428,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext, mFingerprintSensors.get(sensorId).getLazySession(), token, id, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, - opPackageName, FingerprintUtils.getInstance(sensorId), sensorId, - createLogger(BiometricsProtoEnums.ACTION_ENROLL, + opPackageName, FingerprintUtils.getInstance(sensorId), + sensorId, createLogger(BiometricsProtoEnums.ACTION_ENROLL, BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), mBiometricContext, mFingerprintSensors.get(sensorId).getSensorProperties(), @@ -496,12 +496,13 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, mAuthenticationStatsCollector), mBiometricContext, isStrongBiometric, - mTaskStackListener, mFingerprintSensors.get(sensorId).getLockoutCache(), + mTaskStackListener, mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication, mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler, Utils.getCurrentStrength(sensorId), - SystemClock.elapsedRealtimeClock()); + SystemClock.elapsedRealtimeClock(), + null /* lockoutTracker */); scheduleForSensor(sensorId, client, new ClientMonitorCallback() { @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java index d559bb1d72f1..4f08f6fbde54 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java @@ -37,12 +37,12 @@ import java.util.function.Supplier; * Fingerprint-specific removal client supporting the * {@link android.hardware.biometrics.fingerprint.IFingerprint} interface. */ -class FingerprintRemovalClient extends RemovalClient<Fingerprint, AidlSession> { +public class FingerprintRemovalClient extends RemovalClient<Fingerprint, AidlSession> { private static final String TAG = "FingerprintRemovalClient"; private final int[] mBiometricIds; - FingerprintRemovalClient(@NonNull Context context, + public FingerprintRemovalClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int[] biometricIds, int userId, @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java index 7a620349075c..ec225a60d54b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java @@ -32,7 +32,6 @@ import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ErrorConsumer; import com.android.server.biometrics.sensors.HalClientMonitor; -import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; @@ -43,19 +42,20 @@ import java.util.function.Supplier; * Updates the framework's lockout cache and notifies clients such as Keyguard when lockout is * cleared. */ -class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> implements ErrorConsumer { +public class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> + implements ErrorConsumer { private static final String TAG = "FingerprintResetLockoutClient"; private final HardwareAuthToken mHardwareAuthToken; - private final LockoutCache mLockoutCache; + private final LockoutTracker mLockoutCache; private final LockoutResetDispatcher mLockoutResetDispatcher; private final int mBiometricStrength; - FingerprintResetLockoutClient(@NonNull Context context, + public FingerprintResetLockoutClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId, @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext, - @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker, + @NonNull byte[] hardwareAuthToken, @NonNull LockoutTracker lockoutTracker, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @Authenticators.Types int biometricStrength) { super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner, @@ -107,10 +107,11 @@ class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> implem * be used instead. */ static void resetLocalLockoutStateToNone(int sensorId, int userId, - @NonNull LockoutCache lockoutTracker, + @NonNull LockoutTracker lockoutTracker, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull AuthSessionCoordinator authSessionCoordinator, @Authenticators.Types int biometricStrength, long requestId) { + lockoutTracker.resetFailedAttemptsForUser(true /* clearAttemptCounter */, userId); lockoutTracker.setLockoutModeForUser(userId, LockoutTracker.LOCKOUT_NONE); lockoutResetDispatcher.notifyLockoutResetCallbacks(sensorId); authSessionCoordinator.resetLockoutFor(userId, biometricStrength, requestId); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java index afa62e2e3173..23d8e1e63f63 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java @@ -32,13 +32,13 @@ import java.util.function.Supplier; /** * Fingerprint-specific revokeChallenge client for the {@link IFingerprint} AIDL HAL interface. */ -class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession> { +public class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession> { - private static final String TAG = "FingerpirntRevokeChallengeClient"; + private static final String TAG = "FingerprintRevokeChallengeClient"; private final long mChallenge; - FingerprintRevokeChallengeClient(@NonNull Context context, + public FingerprintRevokeChallengeClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @@ -57,7 +57,7 @@ class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession } } - void onChallengeRevoked(int sensorId, int userId, long challenge) { + void onChallengeRevoked(long challenge) { final boolean success = challenge == mChallenge; mCallback.onClientFinished(FingerprintRevokeChallengeClient.this, success); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java index 56b85ceb8e6b..893cb8f9b4fc 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java @@ -23,13 +23,9 @@ import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.ITestSessionCallback; -import android.hardware.biometrics.fingerprint.Error; import android.hardware.biometrics.fingerprint.ISession; -import android.hardware.biometrics.fingerprint.ISessionCallback; -import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; -import android.hardware.keymaster.HardwareAuthToken; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -39,34 +35,25 @@ import android.os.UserManager; import android.util.Slog; import android.util.proto.ProtoOutputStream; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; -import com.android.server.biometrics.HardwareAuthTokenUtils; import com.android.server.biometrics.SensorServiceStateProto; import com.android.server.biometrics.SensorStateProto; import com.android.server.biometrics.UserStateProto; import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; -import com.android.server.biometrics.sensors.AcquisitionClient; -import com.android.server.biometrics.sensors.AuthSessionCoordinator; -import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricStateCallback; -import com.android.server.biometrics.sensors.EnumerateConsumer; import com.android.server.biometrics.sensors.ErrorConsumer; import com.android.server.biometrics.sensors.LockoutCache; -import com.android.server.biometrics.sensors.LockoutConsumer; import com.android.server.biometrics.sensors.LockoutResetDispatcher; -import com.android.server.biometrics.sensors.RemovalConsumer; import com.android.server.biometrics.sensors.StartUserClient; import com.android.server.biometrics.sensors.StopUserClient; import com.android.server.biometrics.sensors.UserAwareBiometricScheduler; import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; @@ -93,348 +80,6 @@ public class Sensor { @Nullable AidlSession mCurrentSession; @NonNull private final Supplier<AidlSession> mLazySession; - @VisibleForTesting - public static class HalSessionCallback extends ISessionCallback.Stub { - - /** - * Interface to sends results to the HalSessionCallback's owner. - */ - public interface Callback { - /** - * Invoked when the HAL sends ERROR_HW_UNAVAILABLE. - */ - void onHardwareUnavailable(); - } - - @NonNull - private final Context mContext; - @NonNull - private final Handler mHandler; - @NonNull - private final String mTag; - @NonNull - private final UserAwareBiometricScheduler mScheduler; - private final int mSensorId; - private final int mUserId; - @NonNull - private final LockoutCache mLockoutCache; - @NonNull - private final LockoutResetDispatcher mLockoutResetDispatcher; - @NonNull - private AuthSessionCoordinator mAuthSessionCoordinator; - @NonNull - private final Callback mCallback; - - HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag, - @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId, - @NonNull LockoutCache lockoutTracker, - @NonNull LockoutResetDispatcher lockoutResetDispatcher, - @NonNull AuthSessionCoordinator authSessionCoordinator, - @NonNull Callback callback) { - mContext = context; - mHandler = handler; - mTag = tag; - mScheduler = scheduler; - mSensorId = sensorId; - mUserId = userId; - mLockoutCache = lockoutTracker; - mLockoutResetDispatcher = lockoutResetDispatcher; - mAuthSessionCoordinator = authSessionCoordinator; - mCallback = callback; - } - - @Override - public int getInterfaceVersion() { - return this.VERSION; - } - - @Override - public String getInterfaceHash() { - return this.HASH; - } - - @Override - public void onChallengeGenerated(long challenge) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintGenerateChallengeClient)) { - Slog.e(mTag, "onChallengeGenerated for wrong client: " - + Utils.getClientName(client)); - return; - } - - final FingerprintGenerateChallengeClient generateChallengeClient = - (FingerprintGenerateChallengeClient) client; - generateChallengeClient.onChallengeGenerated(mSensorId, mUserId, challenge); - }); - } - - @Override - public void onChallengeRevoked(long challenge) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintRevokeChallengeClient)) { - Slog.e(mTag, "onChallengeRevoked for wrong client: " - + Utils.getClientName(client)); - return; - } - - final FingerprintRevokeChallengeClient revokeChallengeClient = - (FingerprintRevokeChallengeClient) client; - revokeChallengeClient.onChallengeRevoked(mSensorId, mUserId, challenge); - }); - } - - @Override - public void onAcquired(byte info, int vendorCode) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof AcquisitionClient)) { - Slog.e(mTag, "onAcquired for non-acquisition client: " - + Utils.getClientName(client)); - return; - } - - final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client; - acquisitionClient.onAcquired(AidlConversionUtils.toFrameworkAcquiredInfo(info), - vendorCode); - }); - } - - @Override - public void onError(byte error, int vendorCode) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - Slog.d(mTag, "onError" - + ", client: " + Utils.getClientName(client) - + ", error: " + error - + ", vendorCode: " + vendorCode); - if (!(client instanceof ErrorConsumer)) { - Slog.e(mTag, "onError for non-error consumer: " - + Utils.getClientName(client)); - return; - } - - final ErrorConsumer errorConsumer = (ErrorConsumer) client; - errorConsumer.onError(AidlConversionUtils.toFrameworkError(error), vendorCode); - - if (error == Error.HW_UNAVAILABLE) { - mCallback.onHardwareUnavailable(); - } - }); - } - - @Override - public void onEnrollmentProgress(int enrollmentId, int remaining) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintEnrollClient)) { - Slog.e(mTag, "onEnrollmentProgress for non-enroll client: " - + Utils.getClientName(client)); - return; - } - - final int currentUserId = client.getTargetUserId(); - final CharSequence name = FingerprintUtils.getInstance(mSensorId) - .getUniqueName(mContext, currentUserId); - final Fingerprint fingerprint = new Fingerprint(name, enrollmentId, mSensorId); - - final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client; - enrollClient.onEnrollResult(fingerprint, remaining); - }); - } - - @Override - public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof AuthenticationConsumer)) { - Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: " - + Utils.getClientName(client)); - return; - } - - final AuthenticationConsumer authenticationConsumer = - (AuthenticationConsumer) client; - final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId); - final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat); - final ArrayList<Byte> byteList = new ArrayList<>(); - for (byte b : byteArray) { - byteList.add(b); - } - - authenticationConsumer.onAuthenticated(fp, true /* authenticated */, byteList); - }); - } - - @Override - public void onAuthenticationFailed() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof AuthenticationConsumer)) { - Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: " - + Utils.getClientName(client)); - return; - } - - final AuthenticationConsumer authenticationConsumer = - (AuthenticationConsumer) client; - final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, mSensorId); - authenticationConsumer - .onAuthenticated(fp, false /* authenticated */, null /* hat */); - }); - } - - @Override - public void onLockoutTimed(long durationMillis) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof LockoutConsumer)) { - Slog.e(mTag, "onLockoutTimed for non-lockout consumer: " - + Utils.getClientName(client)); - return; - } - - final LockoutConsumer lockoutConsumer = (LockoutConsumer) client; - lockoutConsumer.onLockoutTimed(durationMillis); - }); - } - - @Override - public void onLockoutPermanent() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof LockoutConsumer)) { - Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: " - + Utils.getClientName(client)); - return; - } - - final LockoutConsumer lockoutConsumer = (LockoutConsumer) client; - lockoutConsumer.onLockoutPermanent(); - }); - } - - @Override - public void onLockoutCleared() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintResetLockoutClient)) { - Slog.d(mTag, "onLockoutCleared outside of resetLockout by HAL"); - // Given that onLockoutCleared() can happen at any time, and is not necessarily - // coming from a specific client, set this to -1 to indicate it wasn't for a - // specific request. - FingerprintResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId, - mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, - Utils.getCurrentStrength(mSensorId), -1 /* requestId */); - } else { - Slog.d(mTag, "onLockoutCleared after resetLockout"); - final FingerprintResetLockoutClient resetLockoutClient = - (FingerprintResetLockoutClient) client; - resetLockoutClient.onLockoutCleared(); - } - }); - } - - @Override - public void onInteractionDetected() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintDetectClient)) { - Slog.e(mTag, "onInteractionDetected for non-detect client: " - + Utils.getClientName(client)); - return; - } - - final FingerprintDetectClient fingerprintDetectClient = - (FingerprintDetectClient) client; - fingerprintDetectClient.onInteractionDetected(); - }); - } - - @Override - public void onEnrollmentsEnumerated(int[] enrollmentIds) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof EnumerateConsumer)) { - Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: " - + Utils.getClientName(client)); - return; - } - - final EnumerateConsumer enumerateConsumer = - (EnumerateConsumer) client; - if (enrollmentIds.length > 0) { - for (int i = 0; i < enrollmentIds.length; i++) { - final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId); - enumerateConsumer.onEnumerationResult(fp, enrollmentIds.length - i - 1); - } - } else { - enumerateConsumer.onEnumerationResult(null /* identifier */, 0); - } - }); - } - - @Override - public void onEnrollmentsRemoved(int[] enrollmentIds) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof RemovalConsumer)) { - Slog.e(mTag, "onRemoved for non-removal consumer: " - + Utils.getClientName(client)); - return; - } - - final RemovalConsumer removalConsumer = (RemovalConsumer) client; - if (enrollmentIds.length > 0) { - for (int i = 0; i < enrollmentIds.length; i++) { - final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId); - removalConsumer.onRemoved(fp, enrollmentIds.length - i - 1); - } - } else { - removalConsumer.onRemoved(null, 0); - } - }); - } - - @Override - public void onAuthenticatorIdRetrieved(long authenticatorId) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintGetAuthenticatorIdClient)) { - Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: " - + Utils.getClientName(client)); - return; - } - - final FingerprintGetAuthenticatorIdClient getAuthenticatorIdClient = - (FingerprintGetAuthenticatorIdClient) client; - getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId); - }); - } - - @Override - public void onAuthenticatorIdInvalidated(long newAuthenticatorId) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintInvalidationClient)) { - Slog.e(mTag, "onAuthenticatorIdInvalidated for wrong consumer: " - + Utils.getClientName(client)); - return; - } - - final FingerprintInvalidationClient invalidationClient = - (FingerprintInvalidationClient) client; - invalidationClient.onAuthenticatorIdInvalidated(newAuthenticatorId); - }); - } - - @Override - public void onSessionClosed() { - mHandler.post(mScheduler::onUserStopped); - } - } - Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context, @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @@ -466,9 +111,9 @@ public class Sensor { public StartUserClient<?, ?> getStartUserClient(int newUserId) { final int sensorId = mSensorProperties.sensorId; - final HalSessionCallback resultController = new HalSessionCallback(mContext, - mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache, - lockoutResetDispatcher, + final AidlResponseHandler resultController = new AidlResponseHandler( + mContext, mScheduler, sensorId, newUserId, + mLockoutCache, lockoutResetDispatcher, biometricContext.getAuthSessionCoordinator(), () -> { Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE"); mCurrentSession = null; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java new file mode 100644 index 000000000000..b48d232e65af --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.hidl; + +import android.annotation.NonNull; +import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.common.OperationContext; +import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.biometrics.fingerprint.PointerContext; +import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; +import android.hardware.keymaster.HardwareAuthToken; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper; +import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler; + +import java.util.function.Supplier; + +/** + * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation. + */ +public class AidlToHidlAdapter implements ISession { + private final String TAG = "AidlToHidlAdapter"; + @VisibleForTesting + static final int ENROLL_TIMEOUT_SEC = 60; + @NonNull + private final Supplier<IBiometricsFingerprint> mSession; + private final int mUserId; + private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter; + + public AidlToHidlAdapter(Supplier<IBiometricsFingerprint> session, int userId, + AidlResponseHandler aidlResponseHandler) { + mSession = session; + mUserId = userId; + setCallback(aidlResponseHandler); + } + + private void setCallback(AidlResponseHandler aidlResponseHandler) { + mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler); + try { + mSession.get().setNotify(mHidlToAidlCallbackConverter); + } catch (RemoteException e) { + Slog.d(TAG, "Failed to set callback"); + } + } + + @Override + public IBinder asBinder() { + return null; + } + + @Override + public void generateChallenge() throws RemoteException { + long challenge = mSession.get().preEnroll(); + mHidlToAidlCallbackConverter.onChallengeGenerated(challenge); + } + + @Override + public void revokeChallenge(long challenge) throws RemoteException { + mSession.get().postEnroll(); + mHidlToAidlCallbackConverter.onChallengeRevoked(0L); + } + + @Override + public ICancellationSignal enroll(HardwareAuthToken hat) throws RemoteException { + mSession.get().enroll(HardwareAuthTokenUtils.toByteArray(hat), mUserId, + ENROLL_TIMEOUT_SEC); + return new Cancellation(); + } + + @Override + public ICancellationSignal authenticate(long operationId) throws RemoteException { + mSession.get().authenticate(operationId, mUserId); + return new Cancellation(); + } + + @Override + public ICancellationSignal detectInteraction() throws RemoteException { + mSession.get().authenticate(0, mUserId); + return new Cancellation(); + } + + @Override + public void enumerateEnrollments() throws RemoteException { + mSession.get().enumerate(); + } + + @Override + public void removeEnrollments(int[] enrollmentIds) throws RemoteException { + if (enrollmentIds.length > 1) { + mSession.get().remove(mUserId, 0); + } else { + mSession.get().remove(mUserId, enrollmentIds[0]); + } + } + + @Override + public void onPointerDown(int pointerId, int x, int y, float minor, float major) + throws RemoteException { + UdfpsHelper.onFingerDown(mSession.get(), x, y, minor, major); + } + + @Override + public void onPointerUp(int pointerId) throws RemoteException { + UdfpsHelper.onFingerUp(mSession.get()); + } + + @Override + public void getAuthenticatorId() throws RemoteException { + //Unsupported in HIDL + } + + @Override + public void invalidateAuthenticatorId() throws RemoteException { + //Unsupported in HIDL + } + + @Override + public void resetLockout(HardwareAuthToken hat) throws RemoteException { + mHidlToAidlCallbackConverter.onResetLockout(); + } + + @Override + public void close() throws RemoteException { + //Unsupported in HIDL + } + + @Override + public void onUiReady() throws RemoteException { + //Unsupported in HIDL + } + + @Override + public ICancellationSignal authenticateWithContext(long operationId, OperationContext context) + throws RemoteException { + //Unsupported in HIDL + return null; + } + + @Override + public ICancellationSignal enrollWithContext(HardwareAuthToken hat, OperationContext context) + throws RemoteException { + //Unsupported in HIDL + return null; + } + + @Override + public ICancellationSignal detectInteractionWithContext(OperationContext context) + throws RemoteException { + //Unsupported in HIDL + return null; + } + + @Override + public void onPointerDownWithContext(PointerContext context) throws RemoteException { + //Unsupported in HIDL + } + + @Override + public void onPointerUpWithContext(PointerContext context) throws RemoteException { + //Unsupported in HIDL + } + + @Override + public void onContextChanged(OperationContext context) throws RemoteException { + //Unsupported in HIDL + } + + @Override + public void onPointerCancelWithContext(PointerContext context) throws RemoteException { + //Unsupported in HIDL + } + + @Override + public void setIgnoreDisplayTouches(boolean shouldIgnore) throws RemoteException { + //Unsupported in HIDL + } + + @Override + public int getInterfaceVersion() throws RemoteException { + //Unsupported in HIDL + return 0; + } + + @Override + public String getInterfaceHash() throws RemoteException { + //Unsupported in HIDL + return null; + } + + private class Cancellation extends ICancellationSignal.Stub { + + Cancellation() {} + @Override + public void cancel() throws RemoteException { + try { + mSession.get().cancel(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when requesting cancel", e); + } + } + + @Override + public int getInterfaceVersion() throws RemoteException { + return 0; + } + + @Override + public String getInterfaceHash() throws RemoteException { + return null; + } + } +} 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 a655f3601a07..8bfa560b8031 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 @@ -54,6 +54,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver; import com.android.server.biometrics.AuthenticationStatsCollector; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.SensorServiceStateProto; import com.android.server.biometrics.SensorStateProto; import com.android.server.biometrics.UserStateProto; @@ -64,6 +65,7 @@ import com.android.server.biometrics.fingerprint.PerformanceStatsProto; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AcquisitionClient; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.BaseClientMonitor; @@ -74,6 +76,7 @@ import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; import com.android.server.biometrics.sensors.EnumerateConsumer; import com.android.server.biometrics.sensors.ErrorConsumer; +import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.PerformanceTracker; @@ -82,6 +85,8 @@ import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; import com.android.server.biometrics.sensors.fingerprint.ServiceProvider; import com.android.server.biometrics.sensors.fingerprint.Udfps; +import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler; +import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession; import org.json.JSONArray; import org.json.JSONException; @@ -132,6 +137,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider private final boolean mIsUdfps; private final int mSensorId; private final boolean mIsPowerbuttonFps; + private AidlSession mSession; private final class BiometricTaskStackListener extends TaskStackListener { @Override @@ -413,6 +419,20 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider }); } + synchronized AidlSession getSession() { + if (mDaemon != null && mSession != null) { + return mSession; + } else { + return mSession = new AidlSession(this::getDaemon, + mCurrentUserId, new AidlResponseHandler(mContext, + mScheduler, mSensorId, mCurrentUserId, new LockoutCache(), + mLockoutResetDispatcher, new AuthSessionCoordinator(), () -> { + mDaemon = null; + mCurrentUserId = UserHandle.USER_NULL; + })); + } + } + @VisibleForTesting synchronized IBiometricsFingerprint getDaemon() { if (mTestHalEnabled) { @@ -518,6 +538,10 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { if (success) { + if (mCurrentUserId != targetUserId) { + // Create new session with updated user ID + mSession = null; + } mCurrentUserId = targetUserId; } else { Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId); @@ -554,47 +578,116 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider // Fingerprint2.1 keeps track of lockout in the framework. Let's just do it on the handler // thread. mHandler.post(() -> { - final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext, - userId, mContext.getOpPackageName(), sensorId, - createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN, - mAuthenticationStatsCollector), - mBiometricContext, mLockoutTracker); - mScheduler.scheduleClientMonitor(client); + if (Flags.deHidl()) { + scheduleResetLockoutAidl(sensorId, userId, hardwareAuthToken); + } else { + scheduleResetLockoutHidl(sensorId, userId); + } }); } + private void scheduleResetLockoutAidl(int sensorId, int userId, + @Nullable byte[] hardwareAuthToken) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient( + mContext, this::getSession, userId, mContext.getOpPackageName(), + sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext, hardwareAuthToken, mLockoutTracker, + mLockoutResetDispatcher, + Utils.getCurrentStrength(sensorId)); + mScheduler.scheduleClientMonitor(client); + } + + private void scheduleResetLockoutHidl(int sensorId, int userId) { + final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext, + userId, mContext.getOpPackageName(), sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext, mLockoutTracker); + mScheduler.scheduleClientMonitor(client); + } + @Override public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token, @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) { mHandler.post(() -> { - final FingerprintGenerateChallengeClient client = - new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token, - new ClientMonitorCallbackConverter(receiver), userId, opPackageName, - mSensorProperties.sensorId, - createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN, - mAuthenticationStatsCollector), - mBiometricContext); - mScheduler.scheduleClientMonitor(client); + if (Flags.deHidl()) { + scheduleGenerateChallengeAidl(userId, token, receiver, opPackageName); + } else { + scheduleGenerateChallengeHidl(userId, token, receiver, opPackageName); + } }); } + private void scheduleGenerateChallengeAidl(int userId, @NonNull IBinder token, + @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGenerateChallengeClient client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGenerateChallengeClient( + mContext, this::getSession, token, + new ClientMonitorCallbackConverter(receiver), userId, opPackageName, + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext); + mScheduler.scheduleClientMonitor(client); + } + + private void scheduleGenerateChallengeHidl(int userId, @NonNull IBinder token, + @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) { + final FingerprintGenerateChallengeClient client = + new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token, + new ClientMonitorCallbackConverter(receiver), userId, opPackageName, + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext); + mScheduler.scheduleClientMonitor(client); + } + @Override public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token, @NonNull String opPackageName, long challenge) { mHandler.post(() -> { - final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient( - mContext, mLazyDaemon, token, userId, opPackageName, - mSensorProperties.sensorId, - createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN, - mAuthenticationStatsCollector), - mBiometricContext); - mScheduler.scheduleClientMonitor(client); + if (Flags.deHidl()) { + scheduleRevokeChallengeAidl(userId, token, opPackageName); + } else { + scheduleRevokeChallengeHidl(userId, token, opPackageName); + } }); } + private void scheduleRevokeChallengeAidl(int userId, @NonNull IBinder token, + @NonNull String opPackageName) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRevokeChallengeClient client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRevokeChallengeClient( + mContext, this::getSession, + token, userId, opPackageName, + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext, 0L); + mScheduler.scheduleClientMonitor(client); + } + + private void scheduleRevokeChallengeHidl(int userId, @NonNull IBinder token, + @NonNull String opPackageName) { + final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient( + mContext, mLazyDaemon, token, userId, opPackageName, + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext); + mScheduler.scheduleClientMonitor(client); + } + @Override public long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @@ -604,38 +697,96 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); - final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext, - mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver), - userId, hardwareAuthToken, opPackageName, - FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC, - mSensorProperties.sensorId, - createLogger(BiometricsProtoEnums.ACTION_ENROLL, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason); - mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { - @Override - public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { - mBiometricStateCallback.onClientStarted(clientMonitor); - } + if (Flags.deHidl()) { + scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver, + opPackageName, enrollReason, id); + } else { + scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver, + opPackageName, enrollReason, id); + } + }); + return id; + } - @Override - public void onBiometricAction(int action) { - mBiometricStateCallback.onBiometricAction(action); + private void scheduleEnrollHidl(@NonNull IBinder token, + @NonNull byte[] hardwareAuthToken, int userId, + @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, + @FingerprintManager.EnrollReason int enrollReason, long id) { + final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext, + mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver), + userId, hardwareAuthToken, opPackageName, + FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC, + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_ENROLL, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason); + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + mBiometricStateCallback.onClientStarted(clientMonitor); + } + + @Override + public void onBiometricAction(int action) { + mBiometricStateCallback.onBiometricAction(action); + } + + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + mBiometricStateCallback.onClientFinished(clientMonitor, success); + if (success) { + // Update authenticatorIds + scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(), + true /* force */); } + } + }); + } - @Override - public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, - boolean success) { - mBiometricStateCallback.onClientFinished(clientMonitor, success); - if (success) { - // Update authenticatorIds - scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(), - true /* force */); - } + private void scheduleEnrollAidl(@NonNull IBinder token, + @NonNull byte[] hardwareAuthToken, int userId, + @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, + @FingerprintManager.EnrollReason int enrollReason, long id) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient + client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient( + mContext, + this::getSession, token, id, + new ClientMonitorCallbackConverter(receiver), + userId, hardwareAuthToken, opPackageName, + FingerprintUtils.getLegacyInstance(mSensorId), + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_ENROLL, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext, null /* sensorProps */, + mUdfpsOverlayController, mSidefpsController, + mContext.getResources().getInteger( + com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser), + enrollReason); + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + mBiometricStateCallback.onClientStarted(clientMonitor); + } + + @Override + public void onBiometricAction(int action) { + mBiometricStateCallback.onBiometricAction(action); + } + + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + mBiometricStateCallback.onClientFinished(clientMonitor, success); + if (success) { + // Update authenticatorIds + scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(), + true /* force */); } - }); + } }); - return id; } @Override @@ -653,17 +804,46 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider scheduleUpdateActiveUserWithoutHandler(options.getUserId()); final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId); - final FingerprintDetectClient client = new FingerprintDetectClient(mContext, - mLazyDaemon, token, id, listener, options, - createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, - mAuthenticationStatsCollector), - mBiometricContext, mUdfpsOverlayController, isStrongBiometric); - mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + + if (Flags.deHidl()) { + scheduleFingerDetectAidl(token, listener, options, statsClient, id, + isStrongBiometric); + } else { + scheduleFingerDetectHidl(token, listener, options, statsClient, id, + isStrongBiometric); + } }); return id; } + private void scheduleFingerDetectHidl(@NonNull IBinder token, + @NonNull ClientMonitorCallbackConverter listener, + @NonNull FingerprintAuthenticateOptions options, + int statsClient, long id, boolean isStrongBiometric) { + final FingerprintDetectClient client = new FingerprintDetectClient(mContext, + mLazyDaemon, token, id, listener, options, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, + mAuthenticationStatsCollector), + mBiometricContext, mUdfpsOverlayController, isStrongBiometric); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + + private void scheduleFingerDetectAidl(@NonNull IBinder token, + @NonNull ClientMonitorCallbackConverter listener, + @NonNull FingerprintAuthenticateOptions options, + int statsClient, long id, boolean isStrongBiometric) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintDetectClient + client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintDetectClient( + mContext, + this::getSession, token, id, listener, options, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, + mAuthenticationStatsCollector), + mBiometricContext, mUdfpsOverlayController, isStrongBiometric); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + @Override public void scheduleAuthenticate(@NonNull IBinder token, long operationId, int cookie, @NonNull ClientMonitorCallbackConverter listener, @@ -674,20 +854,55 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider scheduleUpdateActiveUserWithoutHandler(options.getUserId()); final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId); - final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient( - mContext, mLazyDaemon, token, requestId, listener, operationId, - restricted, options, cookie, false /* requireConfirmation */, - createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, - mAuthenticationStatsCollector), - mBiometricContext, isStrongBiometric, - mTaskStackListener, mLockoutTracker, - mUdfpsOverlayController, mSidefpsController, - allowBackgroundAuthentication, mSensorProperties, - Utils.getCurrentStrength(mSensorId)); - mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + + if (Flags.deHidl()) { + scheduleAuthenticateAidl(token, operationId, cookie, listener, options, requestId, + restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric); + } else { + scheduleAuthenticateHidl(token, operationId, cookie, listener, options, requestId, + restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric); + } }); } + private void scheduleAuthenticateAidl(@NonNull IBinder token, long operationId, + int cookie, @NonNull ClientMonitorCallbackConverter listener, + @NonNull FingerprintAuthenticateOptions options, + long requestId, boolean restricted, int statsClient, + boolean allowBackgroundAuthentication, boolean isStrongBiometric) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintAuthenticationClient + client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintAuthenticationClient( + mContext, this::getSession, token, requestId, listener, operationId, + restricted, options, cookie, false /* requireConfirmation */, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, + mAuthenticationStatsCollector), + mBiometricContext, isStrongBiometric, + mTaskStackListener, + mUdfpsOverlayController, mSidefpsController, + allowBackgroundAuthentication, mSensorProperties, mHandler, + Utils.getCurrentStrength(mSensorId), null /* clock */, mLockoutTracker); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + + private void scheduleAuthenticateHidl(@NonNull IBinder token, long operationId, + int cookie, @NonNull ClientMonitorCallbackConverter listener, + @NonNull FingerprintAuthenticateOptions options, + long requestId, boolean restricted, int statsClient, + boolean allowBackgroundAuthentication, boolean isStrongBiometric) { + final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient( + mContext, mLazyDaemon, token, requestId, listener, operationId, + restricted, options, cookie, false /* requireConfirmation */, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, + mAuthenticationStatsCollector), + mBiometricContext, isStrongBiometric, + mTaskStackListener, mLockoutTracker, + mUdfpsOverlayController, mSidefpsController, + allowBackgroundAuthentication, mSensorProperties, + Utils.getCurrentStrength(mSensorId)); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + @Override public long scheduleAuthenticate(@NonNull IBinder token, long operationId, int cookie, @NonNull ClientMonitorCallbackConverter listener, @@ -719,17 +934,41 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); - final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext, - mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId, - userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), - mSensorProperties.sensorId, - createLogger(BiometricsProtoEnums.ACTION_REMOVE, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, mAuthenticatorIds); - mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + if (Flags.deHidl()) { + scheduleRemoveAidl(token, receiver, fingerId, userId, opPackageName); + } else { + scheduleRemoveHidl(token, receiver, fingerId, userId, opPackageName); + } }); } + private void scheduleRemoveHidl(@NonNull IBinder token, + @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId, + @NonNull String opPackageName) { + final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext, + mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId, + userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_REMOVE, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + + private void scheduleRemoveAidl(@NonNull IBinder token, + @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId, + @NonNull String opPackageName) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRemovalClient client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRemovalClient( + mContext, this::getSession, token, + new ClientMonitorCallbackConverter(receiver), new int[]{fingerId}, userId, + opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), mSensorId, + createLogger(BiometricsProtoEnums.ACTION_REMOVE, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + @Override public void scheduleRemoveAll(int sensorId, @NonNull IBinder token, @NonNull IFingerprintServiceReceiver receiver, int userId, @@ -738,15 +977,11 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider scheduleUpdateActiveUserWithoutHandler(userId); // For IBiometricsFingerprint@2.1, remove(0) means remove all enrollments - final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext, - mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), - 0 /* fingerprintId */, userId, opPackageName, - FingerprintUtils.getLegacyInstance(mSensorId), - mSensorProperties.sensorId, - createLogger(BiometricsProtoEnums.ACTION_REMOVE, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, mAuthenticatorIds); - mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + if (Flags.deHidl()) { + scheduleRemoveAidl(token, receiver, 0 /* fingerId */, userId, opPackageName); + } else { + scheduleRemoveHidl(token, receiver, 0 /* fingerId */, userId, opPackageName); + } }); } @@ -755,17 +990,41 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); - final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient( - mContext, mLazyDaemon, userId, mContext.getOpPackageName(), - mSensorProperties.sensorId, - createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, - FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); - mScheduler.scheduleClientMonitor(client, callback); + if (Flags.deHidl()) { + scheduleInternalCleanupAidl(userId, callback); + } else { + scheduleInternalCleanupHidl(userId, callback); + } }); } + private void scheduleInternalCleanupHidl(int userId, + @Nullable ClientMonitorCallback callback) { + final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient( + mContext, mLazyDaemon, userId, mContext.getOpPackageName(), + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, + FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, callback); + } + + private void scheduleInternalCleanupAidl(int userId, + @Nullable ClientMonitorCallback callback) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInternalCleanupClient + client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInternalCleanupClient( + mContext, this::getSession, userId, mContext.getOpPackageName(), + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext, + FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, callback); + } + @Override public void scheduleInternalCleanup(int sensorId, int userId, @Nullable ClientMonitorCallback callback) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java new file mode 100644 index 000000000000..c3e5cbe7daf4 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.hidl; + +import android.annotation.NonNull; +import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback; + +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler; + +import java.util.ArrayList; + +/** + * Convert HIDL-specific callback interface {@link IBiometricsFingerprintClientCallback} to AIDL + * response handler. + */ +public class HidlToAidlCallbackConverter extends IBiometricsFingerprintClientCallback.Stub { + + final AidlResponseHandler mAidlResponseHandler; + + public HidlToAidlCallbackConverter(@NonNull AidlResponseHandler aidlResponseHandler) { + mAidlResponseHandler = aidlResponseHandler; + } + + @Override + public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) { + mAidlResponseHandler.onEnrollmentProgress(fingerId, remaining); + } + + @Override + public void onAcquired(long deviceId, int acquiredInfo, int vendorCode) { + onAcquired_2_2(deviceId, acquiredInfo, vendorCode); + } + + @Override + public void onAcquired_2_2(long deviceId, int acquiredInfo, int vendorCode) { + mAidlResponseHandler.onAcquired(acquiredInfo, vendorCode); + } + + @Override + public void onAuthenticated(long deviceId, int fingerId, int groupId, + ArrayList<Byte> token) { + if (fingerId != 0) { + byte[] hardwareAuthToken = new byte[token.size()]; + for (int i = 0; i < token.size(); i++) { + hardwareAuthToken[i] = token.get(i); + } + mAidlResponseHandler.onAuthenticationSucceeded(fingerId, + HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken)); + } else { + mAidlResponseHandler.onAuthenticationFailed(); + } + } + + @Override + public void onError(long deviceId, int error, int vendorCode) { + mAidlResponseHandler.onError(error, vendorCode); + } + + @Override + public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) { + mAidlResponseHandler.onEnrollmentsRemoved(new int[]{fingerId}); + } + + @Override + public void onEnumerate(long deviceId, int fingerId, int groupId, int remaining) { + mAidlResponseHandler.onEnrollmentsEnumerated(new int[]{fingerId}); + } + + void onChallengeGenerated(long challenge) { + mAidlResponseHandler.onChallengeGenerated(challenge); + } + + void onChallengeRevoked(long challenge) { + mAidlResponseHandler.onChallengeRevoked(challenge); + } + + void onResetLockout() { + mAidlResponseHandler.onLockoutCleared(); + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java index 36d56c8a1544..0730c672acd9 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java @@ -89,7 +89,8 @@ public class LockoutFrameworkImpl implements LockoutTracker { // Attempt counter should only be cleared when Keyguard goes away or when // a biometric is successfully authenticated. Lockout should eventually be done below the HAL. // See AuthenticationClient#shouldFrameworkHandleLockout(). - void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) { + @Override + public void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) { if (getLockoutModeForUser(userId) != LOCKOUT_NONE) { Slog.v(TAG, "Reset biometric lockout for user: " + userId + ", clearAttemptCounter: " + clearAttemptCounter); @@ -104,7 +105,8 @@ public class LockoutFrameworkImpl implements LockoutTracker { mLockoutResetCallback.onLockoutReset(userId); } - void addFailedAttemptForUser(int userId) { + @Override + public void addFailedAttemptForUser(int userId) { mFailedAttempts.put(userId, mFailedAttempts.get(userId, 0) + 1); mTimedLockoutCleared.put(userId, false); @@ -114,7 +116,8 @@ public class LockoutFrameworkImpl implements LockoutTracker { } @Override - public @LockoutMode int getLockoutModeForUser(int userId) { + @LockoutMode + public int getLockoutModeForUser(int userId) { final int failedAttempts = mFailedAttempts.get(userId, 0); if (failedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) { return LOCKOUT_PERMANENT; @@ -126,6 +129,19 @@ public class LockoutFrameworkImpl implements LockoutTracker { return LOCKOUT_NONE; } + /** + * Clears lockout for Fingerprint HIDL HAL + */ + @Override + public void setLockoutModeForUser(int userId, int mode) { + mFailedAttempts.put(userId, 0); + mTimedLockoutCleared.put(userId, true); + // If we're asked to reset failed attempts externally (i.e. from Keyguard), + // the alarm might still be pending; remove it. + cancelLockoutResetForUser(userId); + mLockoutResetCallback.onLockoutReset(userId); + } + private void cancelLockoutResetForUser(int userId) { mAlarmManager.cancel(getLockoutResetIntentForUser(userId)); } diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java index c7c8136ff0da..79c13461fc6b 100644 --- a/services/core/java/com/android/server/os/NativeTombstoneManager.java +++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java @@ -569,6 +569,10 @@ public final class NativeTombstoneManager { @Override public void onEvent(int event, @Nullable String path) { + if (path == null) { + Slog.w(TAG, "path is null at TombstoneWatcher.onEvent()"); + return; + } mHandler.post(() -> { // Ignore .tmp files. if (path.endsWith(".tmp")) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index c866dd013af0..ebd21d9a0487 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -8264,7 +8264,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private void clearSizeCompatModeAttributes() { mInSizeCompatModeForBounds = false; + final float lastSizeCompatScale = mSizeCompatScale; mSizeCompatScale = 1f; + if (mSizeCompatScale != lastSizeCompatScale) { + forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */); + } mSizeCompatBounds = null; mCompatDisplayInsets = null; mLetterboxUiController.clearInheritedCompatDisplayInsets(); @@ -8272,11 +8276,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @VisibleForTesting void clearSizeCompatMode() { - final float lastSizeCompatScale = mSizeCompatScale; clearSizeCompatModeAttributes(); - if (mSizeCompatScale != lastSizeCompatScale) { - forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */); - } // Clear config override in #updateCompatDisplayInsets(). final int activityType = getActivityType(); final Configuration overrideConfig = getRequestedOverrideConfiguration(); diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index f509463c5409..de7871e3c231 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -1067,12 +1067,20 @@ class TransitionController { // legacy sync mSyncEngine.startSyncSet(queued.mLegacySync); } - // Post this so that the now-playing transition logic isn't interrupted. - mAtm.mH.post(() -> { - synchronized (mAtm.mGlobalLock) { - queued.mOnStartCollect.onCollectStarted(true /* deferred */); - } - }); + if (queued.mTransition != null + && queued.mTransition.mType == WindowManager.TRANSIT_SLEEP) { + // SLEEP transitions are special in that they don't collect anything (in fact if they + // do collect things it can cause problems). So, we need to run it's onCollectStarted + // immediately. + queued.mOnStartCollect.onCollectStarted(true /* deferred */); + } else { + // Post this so that the now-playing transition logic isn't interrupted. + mAtm.mH.post(() -> { + synchronized (mAtm.mGlobalLock) { + queued.mOnStartCollect.onCollectStarted(true /* deferred */); + } + }); + } } void moveToPlaying(Transition transition) { diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 5b519633081a..21e3b34ed61a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -62,8 +62,6 @@ import static com.android.server.alarm.AlarmManagerService.ACTIVE_INDEX; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE; -import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_ADDED; -import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_ALARMS; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED; @@ -75,7 +73,6 @@ import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_W import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_QUOTA; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WINDOW; -import static com.android.server.alarm.AlarmManagerService.Constants.KEY_EXACT_ALARM_DENY_LIST; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_DEVICE_IDLE_FUZZ; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_INTERVAL; @@ -85,7 +82,6 @@ import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_INT import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_WINDOW; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_PRIORITY_ALARM_DELAY; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_TEMPORARY_QUOTA_BUMP; -import static com.android.server.alarm.AlarmManagerService.Constants.MAX_EXACT_ALARM_DENY_LIST_SIZE; import static com.android.server.alarm.AlarmManagerService.FREQUENT_INDEX; import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY; import static com.android.server.alarm.AlarmManagerService.IS_WAKEUP_MASK; @@ -799,47 +795,6 @@ public final class AlarmManagerServiceTest { } @Test - public void updatingExactAlarmDenyList() { - ArraySet<String> denyListed = new ArraySet<>(new String[]{ - "com.example.package1", - "com.example.package2", - "com.example.package3", - }); - setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, - "com.example.package1,com.example.package2,com.example.package3"); - assertEquals(denyListed, mService.mConstants.EXACT_ALARM_DENY_LIST); - - - denyListed = new ArraySet<>(new String[]{ - "com.example.package1", - "com.example.package4", - }); - setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, - "com.example.package1,com.example.package4"); - assertEquals(denyListed, mService.mConstants.EXACT_ALARM_DENY_LIST); - - setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, ""); - assertEquals(0, mService.mConstants.EXACT_ALARM_DENY_LIST.size()); - } - - @Test - public void exactAlarmDenyListMaxSize() { - final ArraySet<String> expectedSet = new ArraySet<>(); - final StringBuilder sb = new StringBuilder("package1"); - expectedSet.add("package1"); - for (int i = 2; i <= 2 * MAX_EXACT_ALARM_DENY_LIST_SIZE; i++) { - sb.append(",package"); - sb.append(i); - if (i <= MAX_EXACT_ALARM_DENY_LIST_SIZE) { - expectedSet.add("package" + i); - } - } - assertEquals(MAX_EXACT_ALARM_DENY_LIST_SIZE, expectedSet.size()); - setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, sb.toString()); - assertEquals(expectedSet, mService.mConstants.EXACT_ALARM_DENY_LIST); - } - - @Test public void positiveWhileIdleQuotas() { setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, -3); assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA); @@ -2212,50 +2167,30 @@ public final class AlarmManagerServiceTest { } @Test - public void hasScheduleExactAlarmBinderCallNotDenyListedPreT() throws RemoteException { + public void hasScheduleExactAlarmBinderCallPreT() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT); + mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT); assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED); assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - mockScheduleExactAlarmStatePreT(true, false, MODE_IGNORED); + mockScheduleExactAlarmStatePreT(true, MODE_IGNORED); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); } @Test - public void hasScheduleExactAlarmBinderCallDenyListedPreT() throws RemoteException { - mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - - mockScheduleExactAlarmStatePreT(true, true, MODE_ERRORED); - assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - - mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT); - assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - - mockScheduleExactAlarmStatePreT(true, true, MODE_IGNORED); - assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - - mockScheduleExactAlarmStatePreT(true, true, MODE_ALLOWED); - assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - } - - @Test public void hasScheduleExactAlarmBinderCallNotDeclaredPreT() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - mockScheduleExactAlarmStatePreT(false, false, MODE_DEFAULT); - assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - - mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(false, MODE_DEFAULT); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - mockScheduleExactAlarmStatePreT(false, true, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(false, MODE_ALLOWED); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); } @@ -2281,41 +2216,33 @@ public final class AlarmManagerServiceTest { mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true); // No permission, no exemption. - mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT); - assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); - - // No permission, no exemption. - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); // Policy permission only, no exemption. - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); mockUseExactAlarmState(true); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); mockUseExactAlarmState(false); // User permission only, no exemption. - mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT); - assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); - - // User permission only, no exemption. - mockScheduleExactAlarmStatePreT(true, true, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); // No permission, exemption. - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(true); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); // No permission, exemption. - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(false); doReturn(true).when(() -> UserHandle.isCore(TEST_CALLING_UID)); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); // Both permissions and exemption. - mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED); mockUseExactAlarmState(true); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); } @@ -2514,17 +2441,12 @@ public final class AlarmManagerServiceTest { assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } - private void mockScheduleExactAlarmStatePreT(boolean declared, boolean denyList, int mode) { + private void mockScheduleExactAlarmStatePreT(boolean declared, int mode) { String[] requesters = declared ? new String[]{TEST_CALLING_PACKAGE} : EmptyArray.STRING; when(mPermissionManagerInternal.getAppOpPermissionPackages(SCHEDULE_EXACT_ALARM)) .thenReturn(requesters); mService.refreshExactAlarmCandidates(); - if (denyList) { - setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, TEST_CALLING_PACKAGE); - } else { - setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, ""); - } when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE)).thenReturn(mode); } @@ -2556,7 +2478,7 @@ public final class AlarmManagerServiceTest { public void alarmClockBinderCallWithoutPermission() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); final PendingIntent alarmPi = getNewMockPendingIntent(); @@ -2630,7 +2552,7 @@ public final class AlarmManagerServiceTest { public void exactBinderCallWithAllowlist() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); // If permission is denied, only then allowlist will be checked. - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); final PendingIntent alarmPi = getNewMockPendingIntent(); @@ -2650,7 +2572,7 @@ public final class AlarmManagerServiceTest { public void exactAllowWhileIdleBinderCallWithSEAPermission() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); @@ -2676,7 +2598,7 @@ public final class AlarmManagerServiceTest { mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true); mockUseExactAlarmState(true); - mockScheduleExactAlarmStatePreT(false, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(false, MODE_ERRORED); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); @@ -2700,7 +2622,7 @@ public final class AlarmManagerServiceTest { public void exactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); // If permission is denied, only then allowlist will be checked. - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); final PendingIntent alarmPi = getNewMockPendingIntent(); @@ -2726,7 +2648,7 @@ public final class AlarmManagerServiceTest { public void exactBinderCallsWithoutPermissionWithoutAllowlist() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false); final PendingIntent alarmPi = getNewMockPendingIntent(); @@ -2836,7 +2758,7 @@ public final class AlarmManagerServiceTest { public void binderCallWithUserAllowlist() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); when(mAppStateTracker.isUidPowerSaveUserExempt(TEST_CALLING_UID)).thenReturn(true); @@ -3052,135 +2974,11 @@ public final class AlarmManagerServiceTest { } @Test - public void denyListChanged() { - mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{"p1", "p2", "p3"}); - when(mActivityManagerInternal.getStartedUserIds()).thenReturn(EmptyArray.INT); - - setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "p2,p4,p5"); - - final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); - verify(mService.mHandler, times(2)).sendMessageAtTime(messageCaptor.capture(), - anyLong()); - - final List<Message> messages = messageCaptor.getAllValues(); - for (final Message msg : messages) { - assertTrue("Unwanted message sent to handler: " + msg.what, - msg.what == EXACT_ALARM_DENY_LIST_PACKAGES_ADDED - || msg.what == EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED); - mService.mHandler.handleMessage(msg); - } - - ArraySet<String> added = new ArraySet<>(new String[]{"p4", "p5"}); - verify(mService).handleChangesToExactAlarmDenyList(eq(added), eq(true)); - - ArraySet<String> removed = new ArraySet<>(new String[]{"p1", "p3"}); - verify(mService).handleChangesToExactAlarmDenyList(eq(removed), eq(false)); - } - - @Test - public void permissionGrantedDueToDenyList() { - mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - - final String[] packages = {"example.package.1", "example.package.2"}; - - final int appId1 = 232; - final int appId2 = 431; - - final int userId1 = 42; - final int userId2 = 53; - - registerAppIds(packages, new Integer[]{appId1, appId2}); - - when(mActivityManagerInternal.getStartedUserIds()).thenReturn(new int[]{userId1, userId2}); - - when(mPermissionManagerInternal.getAppOpPermissionPackages( - SCHEDULE_EXACT_ALARM)).thenReturn(packages); - mService.refreshExactAlarmCandidates(); - - final long allowListDuration = 53442; - when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn( - allowListDuration); - - mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId1), MODE_ALLOWED); - mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId1), MODE_DEFAULT); - mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId2), MODE_IGNORED); - mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId2), MODE_ERRORED); - - mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), false); - - // No permission revoked. - verify(mService, never()).removeExactAlarmsOnPermissionRevoked(anyInt(), anyString(), - anyBoolean()); - - // Permission got granted only for (appId1, userId2). - final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); - final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); - final ArgumentCaptor<UserHandle> userCaptor = ArgumentCaptor.forClass(UserHandle.class); - - verify(mMockContext).sendBroadcastAsUser(intentCaptor.capture(), userCaptor.capture(), - isNull(), bundleCaptor.capture()); - - assertEquals(userId2, userCaptor.getValue().getIdentifier()); - - // Validate the intent. - assertEquals(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED, - intentCaptor.getValue().getAction()); - assertEquals(packages[0], intentCaptor.getValue().getPackage()); - - // Validate the options. - final BroadcastOptions bOptions = new BroadcastOptions(bundleCaptor.getValue()); - assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, - bOptions.getTemporaryAppAllowlistType()); - assertEquals(allowListDuration, bOptions.getTemporaryAppAllowlistDuration()); - assertEquals(PowerExemptionManager.REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED, - bOptions.getTemporaryAppAllowlistReasonCode()); - } - - @Test - public void permissionRevokedDueToDenyList() { - mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - - final String[] packages = {"example.package.1", "example.package.2"}; - - final int appId1 = 232; - final int appId2 = 431; - - final int userId1 = 42; - final int userId2 = 53; - - registerAppIds(packages, new Integer[]{appId1, appId2}); - - when(mActivityManagerInternal.getStartedUserIds()).thenReturn(new int[]{userId1, userId2}); - - when(mPermissionManagerInternal.getAppOpPermissionPackages( - SCHEDULE_EXACT_ALARM)).thenReturn(packages); - mService.refreshExactAlarmCandidates(); - - mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId1), MODE_ALLOWED); - mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId1), MODE_DEFAULT); - mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId2), MODE_IGNORED); - mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId2), MODE_ERRORED); - - mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), true); - - // Permission got revoked only for (appId1, userId2) - verify(mService, never()).removeExactAlarmsOnPermissionRevoked( - eq(UserHandle.getUid(userId1, appId1)), eq(packages[0]), eq(true)); - verify(mService, never()).removeExactAlarmsOnPermissionRevoked( - eq(UserHandle.getUid(userId1, appId2)), eq(packages[1]), eq(true)); - verify(mService, never()).removeExactAlarmsOnPermissionRevoked( - eq(UserHandle.getUid(userId2, appId2)), eq(packages[1]), eq(true)); - - verify(mService).removeExactAlarmsOnPermissionRevoked( - eq(UserHandle.getUid(userId2, appId1)), eq(packages[0]), eq(true)); - } - - @Test public void opChangedPermissionRevoked() throws Exception { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED); - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); assertAndHandleMessageSync(REMOVE_EXACT_ALARMS); @@ -3193,20 +2991,7 @@ public final class AlarmManagerServiceTest { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED); - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); - - mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); - - verify(mService.mHandler, never()).sendMessageAtTime( - argThat(m -> m.what == REMOVE_EXACT_ALARMS), anyLong()); - } - - @Test - public void opChangedNoPermissionChangeDueToDenyList() throws Exception { - mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); - - mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED); - mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); @@ -3222,7 +3007,7 @@ public final class AlarmManagerServiceTest { when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(durationMs); mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED); - mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED); mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); @@ -3482,7 +3267,7 @@ public final class AlarmManagerServiceTest { .putExtra(Intent.EXTRA_REPLACING, true); mockUseExactAlarmState(false); - mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED); mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent); assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE); @@ -3490,7 +3275,7 @@ public final class AlarmManagerServiceTest { assertEquals(5, mService.mAlarmStore.size()); mockUseExactAlarmState(true); - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent); assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE); @@ -3498,7 +3283,7 @@ public final class AlarmManagerServiceTest { assertEquals(5, mService.mAlarmStore.size()); mockUseExactAlarmState(false); - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent); assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE); @@ -3653,16 +3438,16 @@ public final class AlarmManagerServiceTest { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, false); - mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT); - assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); + mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT); + assertTrue(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); - mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(false, MODE_ALLOWED); assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); - mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED); assertTrue(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); } @@ -3671,11 +3456,11 @@ public final class AlarmManagerServiceTest { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); mockScheduleExactAlarmState(true); - mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED); assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); mockScheduleExactAlarmState(false); - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java index 9e5a0479ea70..3a3dd6ea2746 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java @@ -36,6 +36,7 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.content.ComponentName; +import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.common.AuthenticateReason; import android.hardware.biometrics.common.ICancellationSignal; @@ -59,6 +60,7 @@ import com.android.server.biometrics.log.OperationContextExt; import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.face.UsageStats; import org.junit.Before; @@ -103,7 +105,7 @@ public class FaceAuthenticationClientTest { @Mock private ClientMonitorCallback mCallback; @Mock - private Sensor.HalSessionCallback mHalSessionCallback; + private AidlResponseHandler mAidlResponseHandler; @Mock private ActivityTaskManager mActivityTaskManager; @Mock @@ -112,6 +114,8 @@ public class FaceAuthenticationClientTest { private AuthSessionCoordinator mAuthSessionCoordinator; @Mock private BiometricManager mBiometricManager; + @Mock + private LockoutTracker mLockoutTracker; @Captor private ArgumentCaptor<OperationContextExt> mOperationContextCaptor; @Captor @@ -236,27 +240,63 @@ public class FaceAuthenticationClientTest { verify(mCallback).onClientFinished(client, true); } + @Test + public void authWithNoLockout() throws RemoteException { + when(mLockoutTracker.getLockoutModeForUser(anyInt())).thenReturn( + LockoutTracker.LOCKOUT_NONE); + + final FaceAuthenticationClient client = createClientWithLockoutTracker(mLockoutTracker); + client.start(mCallback); + + verify(mHal).authenticate(OP_ID); + } + + @Test + public void authWithLockout() throws RemoteException { + when(mLockoutTracker.getLockoutModeForUser(anyInt())).thenReturn( + LockoutTracker.LOCKOUT_PERMANENT); + + final FaceAuthenticationClient client = createClientWithLockoutTracker(mLockoutTracker); + client.start(mCallback); + + verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(), + eq(BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT), anyInt()); + verify(mHal, never()).authenticate(anyInt()); + } + private FaceAuthenticationClient createClient() throws RemoteException { return createClient(2 /* version */, mClientMonitorCallbackConverter, - false /* allowBackgroundAuthentication */); + false /* allowBackgroundAuthentication */, + null /* lockoutTracker */); } private FaceAuthenticationClient createClientWithNullListener() throws RemoteException { return createClient(2 /* version */, null /* listener */, - true /* allowBackgroundAuthentication */); + true /* allowBackgroundAuthentication */, + null /* lockoutTracker */); } private FaceAuthenticationClient createClient(int version) throws RemoteException { return createClient(version, mClientMonitorCallbackConverter, - false /* allowBackgroundAuthentication */); + false /* allowBackgroundAuthentication */, + null /* lockoutTracker */); + } + + private FaceAuthenticationClient createClientWithLockoutTracker(LockoutTracker lockoutTracker) + throws RemoteException { + return createClient(0 /* version */, + mClientMonitorCallbackConverter, + true /* allowBackgroundAuthentication */, + lockoutTracker); } private FaceAuthenticationClient createClient(int version, ClientMonitorCallbackConverter listener, - boolean allowBackgroundAuthentication) throws RemoteException { + boolean allowBackgroundAuthentication, + LockoutTracker lockoutTracker) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); - final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler); final FaceAuthenticateOptions options = new FaceAuthenticateOptions.Builder() .setOpPackageName("test-owner") .setUserId(USER_ID) @@ -270,7 +310,7 @@ public class FaceAuthenticationClientTest { false /* restricted */, options, 4 /* cookie */, false /* requireConfirmation */, mBiometricLogger, mBiometricContext, true /* isStrongBiometric */, - mUsageStats, null /* mLockoutCache */, allowBackgroundAuthentication, + mUsageStats, lockoutTracker, allowBackgroundAuthentication, null /* sensorPrivacyManager */, 0 /* biometricStrength */) { @Override protected ActivityTaskManager getActivityTaskManager() { diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java index ade3e8275157..fbf0e13c2ac9 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java @@ -87,7 +87,7 @@ public class FaceDetectClientTest { @Mock private ClientMonitorCallback mCallback; @Mock - private Sensor.HalSessionCallback mHalSessionCallback; + private AidlResponseHandler mAidlResponseHandler; @Captor private ArgumentCaptor<OperationContextExt> mOperationContextCaptor; @Captor @@ -170,7 +170,7 @@ public class FaceDetectClientTest { private FaceDetectClient createClient(int version) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); - final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler); return new FaceDetectClient(mContext, () -> aidl, mToken, 99 /* requestId */, mClientMonitorCallbackConverter, new FaceAuthenticateOptions.Builder() diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java index 54d116f07805..128f3149e6d4 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java @@ -83,7 +83,7 @@ public class FaceEnrollClientTest { @Mock private ClientMonitorCallback mCallback; @Mock - private Sensor.HalSessionCallback mHalSessionCallback; + private AidlResponseHandler mAidlResponseHandler; @Captor private ArgumentCaptor<OperationContextExt> mOperationContextCaptor; @Captor @@ -150,7 +150,7 @@ public class FaceEnrollClientTest { private FaceEnrollClient createClient(int version) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); - final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler); return new FaceEnrollClient(mContext, () -> aidl, mToken, mClientMonitorCallbackConverter, USER_ID, HAT, "com.foo.bar", 44 /* requestId */, mUtils, new int[0] /* disabledFeatures */, 6 /* timeoutSec */, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java new file mode 100644 index 000000000000..c8bfaa90d863 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.face.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FaceGenerateChallengeClientTest { + private static final String TAG = "FaceGenerateChallengeClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + private static final long CHALLENGE = 200; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallbackConverter mListener; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + + private FaceGenerateChallengeClient mClient; + + @Before + public void setUp() throws RemoteException { + when(mAidlSession.getSession()).thenReturn(mSession); + doAnswer(invocation -> { + mClient.onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE); + return null; + }).when(mSession).generateChallenge(); + } + + @Test + public void generateChallenge() throws RemoteException { + createClient(mListener); + mClient.start(mCallback); + + verify(mListener).onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void generateChallenge_nullListener() { + createClient(null); + mClient.start(mCallback); + + verify(mCallback).onClientFinished(mClient, false); + } + + private void createClient(ClientMonitorCallbackConverter listener) { + mClient = new FaceGenerateChallengeClient(mContext, () -> mAidlSession, mToken, listener, + USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java new file mode 100644 index 000000000000..9d0c84edb366 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.face.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.TestableContext; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.List; + +@Presubmit +@SmallTest +public class FaceGetFeatureClientTest { + private static final String TAG = "FaceGetFeatureClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallbackConverter mListener; + @Mock + private ClientMonitorCallback mCallback; + TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getContext()); + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + + private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION; + private FaceGetFeatureClient mClient; + + @Before + public void setUp() throws RemoteException { + mClient = new FaceGetFeatureClient(mContext, () -> mAidlSession, mToken, mListener, + USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, mFeature); + + when(mAidlSession.getSession()).thenReturn(mSession); + doAnswer(invocation -> { + mClient.onFeatureGet(true, new byte[]{ + AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)}); + return null; + }).when(mSession).getFeatures(); + } + + @Test + public void getFeature() throws RemoteException { + ArgumentCaptor<int[]> featuresToSend = ArgumentCaptor.forClass(int[].class); + ArgumentCaptor<boolean[]> featureState = ArgumentCaptor.forClass(boolean[].class); + mClient.start(mCallback); + + verify(mListener).onFeatureGet(eq(true), featuresToSend.capture(), + featureState.capture()); + assertThat(featuresToSend.getValue()).asList().containsExactlyElementsIn(List.of(mFeature)); + assertThat(featureState.getValue()).asList().containsExactlyElementsIn(List.of(true)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java new file mode 100644 index 000000000000..1b4c01723027 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +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.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.face.ISession; +import android.hardware.face.Face; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import androidx.annotation.NonNull; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Presubmit +@SmallTest +public class FaceInternalCleanupClientTest { + private static final String TAG = "FaceInternalCleanupClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private ClientMonitorCallback mCallback; + @Mock + Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + @Mock + private BiometricUtils<Face> mBiometricUtils; + @Mock + private Map<Integer, Long> mAuthenticatorIds; + + private final List<Face> mEnrolledList = new ArrayList<>(); + private final int mBiometricId = 1; + private final Face mFace = new Face("face", mBiometricId, 1 /* deviceId */); + private FaceInternalCleanupClient mClient; + private List<Integer> mAddedIds; + + @Before + public void setUp() throws RemoteException { + when(mAidlSession.getSession()).thenReturn(mSession); + + mEnrolledList.add(mFace); + mAddedIds = new ArrayList<>(); + mClient = new FaceInternalCleanupClient(mContext, () -> mAidlSession, USER_ID, TAG, + SENSOR_ID, mBiometricLogger, mBiometricContext, mBiometricUtils, + mAuthenticatorIds) { + @Override + protected void onAddUnknownTemplate(int userId, + @NonNull BiometricAuthenticator.Identifier identifier) { + mAddedIds.add(identifier.getBiometricId()); + } + }; + } + + @Test + public void removesUnknownTemplate() throws Exception { + final List<Face> templates = List.of( + new Face("one", 1, 1), + new Face("two", 2, 1) + ); + mClient.start(mCallback); + for (int i = templates.size() - 1; i >= 0; i--) { + mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i); + } + for (int i = templates.size() - 1; i >= 0; i--) { + mClient.getCurrentRemoveClient().onRemoved(templates.get(i), 0); + } + + assertThat(mAddedIds).isEmpty(); + final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class); + + verify(mSession, times(2)).removeEnrollments(captor.capture()); + assertThat(captor.getAllValues().stream() + .flatMap(x -> Arrays.stream(x).boxed()) + .collect(Collectors.toList())) + .containsExactly(1, 2); + verify(mCallback).onClientFinished(eq(mClient), eq(true)); + } + + @Test + public void addsUnknownTemplateWhenVirtualIsEnabled() throws Exception { + mClient.setFavorHalEnrollments(); + final List<Face> templates = List.of( + new Face("one", 1, 1), + new Face("two", 2, 1) + ); + mClient.start(mCallback); + for (int i = templates.size() - 1; i >= 0; i--) { + mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i); + } + + assertThat(mAddedIds).containsExactly(1, 2); + verify(mSession, never()).removeEnrollments(any()); + verify(mCallback).onClientFinished(eq(mClient), eq(true)); + } + + @Test + public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled() { + final List<Face> templates = List.of( + new Face("one", 1, 1), + new Face("two", 2, 1), + new Face("three", 3, 1) + ); + mClient.start(mCallback); + for (int i = templates.size() - 1; i >= 0; i--) { + mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i); + } + + // The first template is removed after enumeration + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(2); + + // Simulate finishing the removal of the first template. + // |remaining| is 0 because one FaceRemovalClient is associated with only one + // biometrics ID. + mClient.getCurrentRemoveClient().onRemoved(templates.get(0), 0); + + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1); + + // Simulate finishing the removal of the second template. + mClient.getCurrentRemoveClient().onRemoved(templates.get(1), 0); + + assertThat(mClient.getUnknownHALTemplates()).isEmpty(); + } + + @Test + public void noUnknownTemplates() throws RemoteException { + mClient.start(mCallback); + mClient.getCurrentEnumerateClient().onEnumerationResult(null, 0); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); + verify(mSession, never()).removeEnrollments(any()); + verify(mCallback).onClientFinished(mClient, true); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java new file mode 100644 index 000000000000..8d74fd1d56be --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.face.ISession; +import android.hardware.face.Face; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; + +@Presubmit +@SmallTest +public class FaceInternalEnumerateClientTest { + private static final String TAG = "FaceInternalEnumerateClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallback mCallback; + @Mock + Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + @Mock + private BiometricUtils<Face> mBiometricUtils; + + private final int mBiometricId = 1; + private final Face mFace = new Face("face", mBiometricId, 1 /* deviceId */); + private FaceInternalEnumerateClient mClient; + + @Before + public void setUp() { + when(mAidlSession.getSession()).thenReturn(mSession); + + final List<Face> enrolled = new ArrayList<>(); + enrolled.add(mFace); + mClient = new FaceInternalEnumerateClient(mContext, () -> mAidlSession, mToken, USER_ID, + TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger, mBiometricContext); + } + + @Test + public void internalCleanupClient_noTemplatesRemaining() throws RemoteException { + doAnswer(invocation -> { + mClient.onEnumerationResult(mFace, 0); + return null; + }).when(mSession).enumerateEnrollments(); + + mClient.start(mCallback); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); + verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt()); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void internalCleanupClient_nullIdentifier_remainingOne() throws RemoteException { + doAnswer(invocation -> { + mClient.onEnumerationResult(null, 1); + return null; + }).when(mSession).enumerateEnrollments(); + + mClient.start(mCallback); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); + verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt()); + verify(mCallback, never()).onClientFinished(mClient, true); + } + + @Test + public void internalCleanupClient_nullIdentifier_noTemplatesRemaining() throws RemoteException { + doAnswer(invocation -> { + mClient.onEnumerationResult(null, 0); + return null; + }).when(mSession).enumerateEnrollments(); + + mClient.start(mCallback); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); + verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void internalCleanupClient_templatesRemaining() throws RemoteException { + final Face identifier = new Face("face", 2, 1); + doAnswer(invocation -> { + mClient.onEnumerationResult(identifier, 1); + return null; + }).when(mSession).enumerateEnrollments(); + + mClient.start(mCallback); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1); + verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt()); + verify(mCallback, never()).onClientFinished(mClient, true); + } + + @Test + public void internalCleanupClient_differentIdentifier_noTemplatesRemaining() + throws RemoteException { + final Face identifier = new Face("face", 2, 1); + doAnswer(invocation -> { + mClient.onEnumerationResult(identifier, 0); + return null; + }).when(mSession).enumerateEnrollments(); + + mClient.start(mCallback); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1); + verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId); + verify(mCallback).onClientFinished(mClient, true); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java index 76a5accc5fe9..1d9e9334f043 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java @@ -76,7 +76,7 @@ public class FaceRemovalClientTest { @Mock private ClientMonitorCallback mCallback; @Mock - private Sensor.HalSessionCallback mHalSessionCallback; + private AidlResponseHandler mAidlResponseHandler; @Mock private BiometricUtils<Face> mUtils; @Mock @@ -115,7 +115,7 @@ public class FaceRemovalClientTest { private FaceRemovalClient createClient(int version, int[] biometricIds) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); - final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler); return new FaceRemovalClient(mContext, () -> aidl, mToken, mClientMonitorCallbackConverter, biometricIds, USER_ID, "own-it", mUtils /* utils */, 5 /* sensorId */, mBiometricLogger, mBiometricContext, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java new file mode 100644 index 000000000000..dbbd69bdd3b5 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.face.ISession; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.LockoutCache; +import com.android.server.biometrics.sensors.LockoutResetDispatcher; +import com.android.server.biometrics.sensors.LockoutTracker; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FaceResetLockoutClientTest { + private static final String TAG = "FaceResetLockoutClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private ClientMonitorCallback mCallback; + @Mock + Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + private final byte[] mHardwareAuthToken = new byte[69]; + @Mock + private LockoutCache mLockoutTracker; + @Mock + private LockoutResetDispatcher mLockoutResetDispatcher; + @Mock + private AuthSessionCoordinator mAuthSessionCoordinator; + + private FaceResetLockoutClient mClient; + + @Before + public void setUp() { + mClient = new FaceResetLockoutClient(mContext, () -> mAidlSession, USER_ID, TAG, SENSOR_ID, + mBiometricLogger, mBiometricContext, mHardwareAuthToken, mLockoutTracker, + mLockoutResetDispatcher, BIOMETRIC_STRONG); + + when(mAidlSession.getSession()).thenReturn(mSession); + when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator); + } + + @Test + public void testResetLockout_onLockoutCleared() throws RemoteException { + doAnswer(invocation -> { + mClient.onLockoutCleared(); + return null; + }).when(mSession).resetLockout(any()); + mClient.start(mCallback); + + verify(mSession).resetLockout(any()); + verify(mAuthSessionCoordinator).resetLockoutFor(USER_ID, BIOMETRIC_STRONG, -1); + verify(mLockoutTracker).setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_NONE); + verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(SENSOR_ID); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void testResetLockout_onError() throws RemoteException { + doAnswer(invocation -> { + mClient.onError(0, 0); + return null; + }).when(mSession).resetLockout(any()); + mClient.start(mCallback); + + verify(mSession).resetLockout(any()); + verify(mAuthSessionCoordinator, never()).resetLockoutFor(USER_ID, + BIOMETRIC_STRONG, -1); + verify(mLockoutTracker, never()).setLockoutModeForUser(USER_ID, + LockoutTracker.LOCKOUT_NONE); + verify(mLockoutResetDispatcher, never()).notifyLockoutResetCallbacks(SENSOR_ID); + verify(mCallback).onClientFinished(mClient, false); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java new file mode 100644 index 000000000000..fb5502a1795d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.face.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FaceRevokeChallengeClientTest { + private static final String TAG = "FaceRevokeChallengeClientTest"; + private static final long CHALLENGE = 200L; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallback mCallback; + @Mock + Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + + private FaceRevokeChallengeClient mClient; + + @Before + public void setUp() { + when(mAidlSession.getSession()).thenReturn(mSession); + + mClient = new FaceRevokeChallengeClient(mContext, () -> mAidlSession, mToken, USER_ID, TAG, + SENSOR_ID, mBiometricLogger, mBiometricContext, CHALLENGE); + } + + @Test + public void revokeChallenge() throws RemoteException { + doAnswer(invocation -> { + mClient.onChallengeRevoked(SENSOR_ID, USER_ID, CHALLENGE); + return null; + }).when(mSession).revokeChallenge(CHALLENGE); + mClient.start(mCallback); + + verify(mSession).revokeChallenge(CHALLENGE); + verify(mCallback).onClientFinished(mClient, true); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java new file mode 100644 index 000000000000..eb8cc9c63e75 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.face.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.TestableContext; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FaceSetFeatureClientTest { + private static final String TAG = "FaceSetFeatureClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallbackConverter mListener; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + + private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION; + private final boolean mEnabled = true; + private final byte[] mHardwareAuthToken = new byte[69]; + private FaceSetFeatureClient mClient; + TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getContext()); + + @Before + public void setUp() { + when(mAidlSession.getSession()).thenReturn(mSession); + + mClient = new FaceSetFeatureClient(mContext, () -> mAidlSession, mToken, mListener, USER_ID, + TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, mFeature, mEnabled, + mHardwareAuthToken); + } + + @Test + public void setFeature_onFeatureSet() throws RemoteException { + doAnswer(invocation -> { + mClient.onFeatureSet(true); + return null; + }).when(mSession).setFeature(any(), + eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)), eq(mEnabled)); + mClient.start(mCallback); + + verify(mSession).setFeature(any(), + eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)), + eq(mEnabled)); + verify(mListener).onFeatureSet(true, mFeature); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void setFeature_onError() throws RemoteException { + doAnswer(invocation -> { + mClient.onError(0, 0); + return null; + }).when(mSession).setFeature(any(), + eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)), + eq(mEnabled)); + mClient.start(mCallback); + + verify(mSession).setFeature(any(), + eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)), + eq(mEnabled)); + verify(mListener).onFeatureSet(false, mFeature); + verify(mCallback).onClientFinished(mClient, false); + } +} 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 be9f52e00b16..7a293e80c183 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 @@ -74,7 +74,7 @@ public class SensorTest { @Mock private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback; @Mock - private Sensor.HalSessionCallback.Callback mHalSessionCallback; + private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback; @Mock private LockoutResetDispatcher mLockoutResetDispatcher; @Mock @@ -94,7 +94,7 @@ public class SensorTest { private final LockoutCache mLockoutCache = new LockoutCache(); private UserAwareBiometricScheduler mScheduler; - private Sensor.HalSessionCallback mHalCallback; + private AidlResponseHandler mHalCallback; @Before public void setUp() { @@ -111,10 +111,9 @@ public class SensorTest { mBiometricService, () -> USER_ID, mUserSwitchCallback); - mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()), - TAG, mScheduler, SENSOR_ID, - USER_ID, mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, - mHalSessionCallback); + mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID, + mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, + mHardwareUnavailableCallback); } @Test @@ -153,11 +152,11 @@ public class SensorTest { sensorProps.commonProps.sensorId = 1; final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal( sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength, - sensorProps.commonProps.maxEnrollmentsPerUser, null, + sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */, sensorProps.sensorType, sensorProps.supportsDetectInteraction, sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */); - final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null, - internalProp, mLockoutResetDispatcher, mBiometricContext); + final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, + null /* handler */, internalProp, mLockoutResetDispatcher, mBiometricContext); mScheduler.reset(); @@ -181,7 +180,7 @@ public class SensorTest { sensorProps.commonProps.sensorId = 1; final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal( sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength, - sensorProps.commonProps.maxEnrollmentsPerUser, null, + sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */, sensorProps.sensorType, sensorProps.supportsDetectInteraction, sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */); final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java new file mode 100644 index 000000000000..9a40e8a7201a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.hidl; + +import static com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter.ENROLL_TIMEOUT_SEC; +import static com.android.server.biometrics.sensors.face.hidl.FaceGenerateChallengeClient.CHALLENGE_TIMEOUT_SEC; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.face.EnrollmentType; +import android.hardware.biometrics.face.Feature; +import android.hardware.biometrics.face.V1_0.IBiometricsFace; +import android.hardware.biometrics.face.V1_0.OptionalBool; +import android.hardware.biometrics.face.V1_0.OptionalUint64; +import android.hardware.biometrics.face.V1_0.Status; +import android.hardware.face.Face; +import android.hardware.face.FaceManager; +import android.hardware.keymaster.HardwareAuthToken; +import android.hardware.keymaster.Timestamp; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.TestableContext; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.face.aidl.AidlConversionUtils; +import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.time.Clock; +import java.util.ArrayList; +import java.util.List; + +@Presubmit +@SmallTest +public class AidlToHidlAdapterTest { + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private IBiometricsFace mSession; + @Mock + FaceManager mFaceManager; + @Mock + private AidlResponseHandler mAidlResponseHandler; + @Mock + private HardwareAuthToken mHardwareAuthToken; + @Mock + private Clock mClock; + + private final long mChallenge = 100L; + private AidlToHidlAdapter mAidlToHidlAdapter; + private final Face mFace = new Face("face" /* name */, 1 /* faceId */, 0 /* deviceId */); + private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_REQUIRE_DIVERSITY; + private final byte[] mFeatures = new byte[]{Feature.REQUIRE_ATTENTION}; + + @Before + public void setUp() throws RemoteException { + TestableContext testableContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getContext()); + testableContext.addMockSystemService(FaceManager.class, mFaceManager); + mAidlToHidlAdapter = new AidlToHidlAdapter(testableContext, () -> mSession, 0 /* userId */, + mAidlResponseHandler, mClock); + mHardwareAuthToken.timestamp = new Timestamp(); + mHardwareAuthToken.mac = new byte[10]; + final OptionalUint64 result = new OptionalUint64(); + result.status = Status.OK; + result.value = mChallenge; + + when(mSession.generateChallenge(anyInt())).thenReturn(result); + when(mFaceManager.getEnrolledFaces(anyInt())).thenReturn(List.of(mFace)); + } + + @Test + public void testGenerateChallengeCache() throws RemoteException { + verify(mSession).setCallback(any()); + + final ArgumentCaptor<Long> challengeCaptor = ArgumentCaptor.forClass(Long.class); + + mAidlToHidlAdapter.generateChallenge(); + + verify(mSession).generateChallenge(CHALLENGE_TIMEOUT_SEC); + verify(mAidlResponseHandler).onChallengeGenerated(challengeCaptor.capture()); + assertThat(challengeCaptor.getValue()).isEqualTo(mChallenge); + + forwardTime(10 /* seconds */); + mAidlToHidlAdapter.generateChallenge(); + forwardTime(20 /* seconds */); + mAidlToHidlAdapter.generateChallenge(); + + //Confirms that the challenge is cached and the hal method is not called again + verifyNoMoreInteractions(mSession); + verify(mAidlResponseHandler, times(3)) + .onChallengeGenerated(mChallenge); + + forwardTime(60 /* seconds */); + mAidlToHidlAdapter.generateChallenge(); + + //HAL method called after challenge has timed out + verify(mSession, times(2)).generateChallenge(CHALLENGE_TIMEOUT_SEC); + } + + @Test + public void testRevokeChallenge_waitsUntilEmpty() throws RemoteException { + for (int i = 0; i < 3; i++) { + mAidlToHidlAdapter.generateChallenge(); + forwardTime(10 /* seconds */); + } + for (int i = 0; i < 3; i++) { + mAidlToHidlAdapter.revokeChallenge(0); + forwardTime((i + 1) * 10 /* seconds */); + } + + verify(mSession).revokeChallenge(); + } + + @Test + public void testRevokeChallenge_timeout() throws RemoteException { + mAidlToHidlAdapter.generateChallenge(); + mAidlToHidlAdapter.generateChallenge(); + forwardTime(700); + mAidlToHidlAdapter.generateChallenge(); + mAidlToHidlAdapter.revokeChallenge(0); + + verify(mSession).revokeChallenge(); + } + + @Test + public void testEnroll() throws RemoteException { + ICancellationSignal cancellationSignal = mAidlToHidlAdapter.enroll(mHardwareAuthToken, + EnrollmentType.DEFAULT, mFeatures, + null /* previewSurface */); + ArgumentCaptor<ArrayList<Integer>> featureCaptor = ArgumentCaptor.forClass(ArrayList.class); + + verify(mSession).enroll(any(), eq(ENROLL_TIMEOUT_SEC), featureCaptor.capture()); + + ArrayList<Integer> features = featureCaptor.getValue(); + + assertThat(features).containsExactly( + AidlConversionUtils.convertAidlToFrameworkFeature(mFeatures[0])); + + cancellationSignal.cancel(); + + verify(mSession).cancel(); + } + + @Test + public void testAuthenticate() throws RemoteException { + final int operationId = 2; + ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId); + + verify(mSession).authenticate(operationId); + + cancellationSignal.cancel(); + + verify(mSession).cancel(); + } + + @Test + public void testDetectInteraction() throws RemoteException { + ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction(); + + verify(mSession).authenticate(0); + + cancellationSignal.cancel(); + + verify(mSession).cancel(); + } + + @Test + public void testEnumerateEnrollments() throws RemoteException { + mAidlToHidlAdapter.enumerateEnrollments(); + + verify(mSession).enumerate(); + } + + @Test + public void testRemoveEnrollment() throws RemoteException { + final int[] enrollments = new int[]{1}; + mAidlToHidlAdapter.removeEnrollments(enrollments); + + verify(mSession).remove(enrollments[0]); + } + + @Test + public void testGetFeatures_onResultSuccess() throws RemoteException { + final OptionalBool result = new OptionalBool(); + result.status = Status.OK; + result.value = true; + ArgumentCaptor<byte[]> featureRetrieved = ArgumentCaptor.forClass(byte[].class); + + when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result); + + mAidlToHidlAdapter.setFeature(mFeature); + mAidlToHidlAdapter.getFeatures(); + + verify(mSession).getFeature(eq(mFeature), anyInt()); + verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture()); + assertThat(featureRetrieved.getValue()[0]).isEqualTo( + AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)); + } + + @Test + public void testGetFeatures_onResultFailed() throws RemoteException { + final OptionalBool result = new OptionalBool(); + result.status = Status.OK; + result.value = false; + ArgumentCaptor<byte[]> featureRetrieved = ArgumentCaptor.forClass(byte[].class); + + when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result); + + mAidlToHidlAdapter.setFeature(mFeature); + mAidlToHidlAdapter.getFeatures(); + + verify(mSession).getFeature(eq(mFeature), anyInt()); + verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture()); + assertThat(featureRetrieved.getValue().length).isEqualTo(0); + } + + @Test + public void testGetFeatures_onStatusFailed() throws RemoteException { + final OptionalBool result = new OptionalBool(); + result.status = Status.INTERNAL_ERROR; + result.value = false; + + when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result); + + mAidlToHidlAdapter.setFeature(mFeature); + mAidlToHidlAdapter.getFeatures(); + + verify(mSession).getFeature(eq(mFeature), anyInt()); + verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any()); + verify(mAidlResponseHandler).onError(BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0); + } + + @Test + public void testGetFeatures_featureNotSet() throws RemoteException { + mAidlToHidlAdapter.getFeatures(); + + verify(mSession, never()).getFeature(eq(mFeature), anyInt()); + verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any()); + } + + @Test + public void testSetFeatureSuccessful() throws RemoteException { + byte feature = Feature.REQUIRE_ATTENTION; + boolean enabled = true; + + when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt())).thenReturn(Status.OK); + + mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled); + + verify(mAidlResponseHandler).onFeatureSet(feature); + } + + @Test + public void testSetFeatureFailed() throws RemoteException { + byte feature = Feature.REQUIRE_ATTENTION; + boolean enabled = true; + + when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt())) + .thenReturn(Status.INTERNAL_ERROR); + + mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled); + + verify(mAidlResponseHandler).onError(BiometricFaceConstants.FACE_ERROR_UNKNOWN, + 0 /* vendorCode */); + } + + @Test + public void testGetAuthenticatorId() throws RemoteException { + final long authenticatorId = 2L; + final OptionalUint64 result = new OptionalUint64(); + result.status = Status.OK; + result.value = authenticatorId; + + when(mSession.getAuthenticatorId()).thenReturn(result); + + mAidlToHidlAdapter.getAuthenticatorId(); + + verify(mSession).getAuthenticatorId(); + verify(mAidlResponseHandler).onAuthenticatorIdRetrieved(authenticatorId); + } + + @Test + public void testResetLockout() throws RemoteException { + mAidlToHidlAdapter.resetLockout(mHardwareAuthToken); + + ArgumentCaptor<ArrayList> hatCaptor = ArgumentCaptor.forClass(ArrayList.class); + + verify(mSession).resetLockout(hatCaptor.capture()); + + assertThat(hatCaptor.getValue()).containsExactlyElementsIn(processHAT(mHardwareAuthToken)); + } + + private ArrayList<Byte> processHAT(HardwareAuthToken hat) { + ArrayList<Byte> hardwareAuthToken = new ArrayList<>(); + for (byte b : HardwareAuthTokenUtils.toByteArray(hat)) { + hardwareAuthToken.add(b); + } + return hardwareAuthToken; + } + + private void forwardTime(long seconds) { + when(mClock.millis()).thenReturn(seconds * 1000); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index 8a11e31014d5..79a528c59f49 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -53,11 +53,15 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.TestableContext; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.CallbackWithProbe; @@ -66,6 +70,7 @@ import com.android.server.biometrics.log.Probe; import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.LockoutTracker; import org.junit.Before; import org.junit.Rule; @@ -102,6 +107,9 @@ public class FingerprintAuthenticationClientTest { InstrumentationRegistry.getInstrumentation().getTargetContext(), null); @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); @Mock private ISession mHal; @@ -124,7 +132,7 @@ public class FingerprintAuthenticationClientTest { @Mock private ClientMonitorCallback mCallback; @Mock - private Sensor.HalSessionCallback mHalSessionCallback; + private AidlResponseHandler mAidlResponseHandler; @Mock private ActivityTaskManager mActivityTaskManager; @Mock @@ -135,6 +143,8 @@ public class FingerprintAuthenticationClientTest { private AuthSessionCoordinator mAuthSessionCoordinator; @Mock private Clock mClock; + @Mock + private LockoutTracker mLockoutTracker; @Captor private ArgumentCaptor<OperationContextExt> mOperationContextCaptor; @Captor @@ -425,37 +435,65 @@ public class FingerprintAuthenticationClientTest { verify(mCallback).onClientFinished(client, true); } + @Test + public void testLockoutTracker_authSuccess() throws RemoteException { + final FingerprintAuthenticationClient client = createClient(1 /* version */, + true /* allowBackgroundAuthentication */, mClientMonitorCallbackConverter, + mLockoutTracker); + client.start(mCallback); + client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, + 2 /* deviceId */), true /* authenticated */, new ArrayList<>()); + + verify(mLockoutTracker).resetFailedAttemptsForUser(true, USER_ID); + verify(mLockoutTracker, never()).addFailedAttemptForUser(anyInt()); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL) + public void testLockoutTracker_authFailed() throws RemoteException { + final FingerprintAuthenticationClient client = createClient(1 /* version */, + true /* allowBackgroundAuthentication */, mClientMonitorCallbackConverter, + mLockoutTracker); + client.start(mCallback); + client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, + 2 /* deviceId */), false /* authenticated */, new ArrayList<>()); + + verify(mLockoutTracker, never()).resetFailedAttemptsForUser(anyBoolean(), anyInt()); + verify(mLockoutTracker).addFailedAttemptForUser(USER_ID); + } + private FingerprintAuthenticationClient createClient() throws RemoteException { return createClient(100 /* version */, true /* allowBackgroundAuthentication */, - mClientMonitorCallbackConverter); + mClientMonitorCallbackConverter, null); } private FingerprintAuthenticationClient createClientWithoutBackgroundAuth() throws RemoteException { return createClient(100 /* version */, false /* allowBackgroundAuthentication */, - mClientMonitorCallbackConverter); + mClientMonitorCallbackConverter, null); } private FingerprintAuthenticationClient createClient(int version) throws RemoteException { return createClient(version, true /* allowBackgroundAuthentication */, - mClientMonitorCallbackConverter); + mClientMonitorCallbackConverter, null); } private FingerprintAuthenticationClient createClientWithNullListener() throws RemoteException { return createClient(100 /* version */, true /* allowBackgroundAuthentication */, - null /* listener */); + null, /* listener */null); } private FingerprintAuthenticationClient createClient(int version, - boolean allowBackgroundAuthentication, ClientMonitorCallbackConverter listener) + boolean allowBackgroundAuthentication, ClientMonitorCallbackConverter listener, + LockoutTracker lockoutTracker) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); - final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler); final FingerprintAuthenticateOptions options = new FingerprintAuthenticateOptions.Builder() .setOpPackageName("test-owner") - .setUserId(5) - .setSensorId(9) + .setUserId(USER_ID) + .setSensorId(SENSOR_ID) .build(); return new FingerprintAuthenticationClient(mContext, () -> aidl, mToken, REQUEST_ID, listener, OP_ID, @@ -463,10 +501,11 @@ public class FingerprintAuthenticationClientTest { false /* requireConfirmation */, mBiometricLogger, mBiometricContext, true /* isStrongBiometric */, - null /* taskStackListener */, null /* lockoutCache */, + null /* taskStackListener */, mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication, mSensorProps, - new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock) { + new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock, + lockoutTracker) { @Override protected ActivityTaskManager getActivityTaskManager() { return mActivityTaskManager; diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java index 78d3a9dd9f9e..a467c84845cd 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java @@ -83,7 +83,7 @@ public class FingerprintDetectClientTest { @Mock private ClientMonitorCallback mCallback; @Mock - private Sensor.HalSessionCallback mHalSessionCallback; + private AidlResponseHandler mAidlResponseHandler; @Captor private ArgumentCaptor<OperationContextExt> mOperationContextCaptor; @Captor @@ -150,7 +150,7 @@ public class FingerprintDetectClientTest { @Test public void testWhenListenerIsNull() { - final AidlSession aidl = new AidlSession(0, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(0, mHal, USER_ID, mAidlResponseHandler); final FingerprintDetectClient client = new FingerprintDetectClient(mContext, () -> aidl, mToken, 6 /* requestId */, null /* listener */, new FingerprintAuthenticateOptions.Builder() @@ -173,7 +173,7 @@ public class FingerprintDetectClientTest { private FingerprintDetectClient createClient(int version) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); - final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler); return new FingerprintDetectClient(mContext, () -> aidl, mToken, 6 /* requestId */, mClientMonitorCallbackConverter, new FingerprintAuthenticateOptions.Builder() diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java index ef253801b118..c7eb1db3e74b 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java @@ -102,7 +102,7 @@ public class FingerprintEnrollClientTest { @Mock private ClientMonitorCallback mCallback; @Mock - private Sensor.HalSessionCallback mHalSessionCallback; + private AidlResponseHandler mAidlResponseHandler; @Mock private Probe mLuxProbe; @Captor @@ -291,7 +291,7 @@ public class FingerprintEnrollClientTest { private FingerprintEnrollClient createClient(int version) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); - final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler); return new FingerprintEnrollClient(mContext, () -> aidl, mToken, REQUEST_ID, mClientMonitorCallbackConverter, 0 /* userId */, HAT, "owner", mBiometricUtils, 8 /* sensorId */, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java new file mode 100644 index 000000000000..840961938cb2 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.fingerprint.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FingerprintGenerateChallengeClientTest { + private static final String TAG = "FingerprintGenerateChallengeClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + private static final long CHALLENGE = 200; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallbackConverter mListener; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + + private FingerprintGenerateChallengeClient mClient; + + @Before + public void setUp() { + when(mAidlSession.getSession()).thenReturn(mSession); + + mClient = new FingerprintGenerateChallengeClient(mContext, () -> mAidlSession, mToken, + mListener, USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext); + } + + @Test + public void generateChallenge() throws RemoteException { + doAnswer(invocation -> { + mClient.onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE); + return null; + }).when(mSession).generateChallenge(); + mClient.start(mCallback); + + verify(mSession).generateChallenge(); + verify(mListener).onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE); + verify(mCallback).onClientFinished(mClient, true); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java index 580644347c84..c9482ceb00f5 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.when; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.fingerprint.ISession; import android.hardware.fingerprint.Fingerprint; +import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.testing.TestableContext; @@ -41,7 +42,6 @@ import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -70,9 +70,9 @@ public class FingerprintInternalCleanupClientTest { InstrumentationRegistry.getInstrumentation().getTargetContext(), null); @Mock - private AidlSession mAidlSession; + ISession mSession; @Mock - private ISession mSession; + private AidlSession mAidlSession; @Mock private BiometricLogger mLogger; @Mock @@ -87,15 +87,16 @@ public class FingerprintInternalCleanupClientTest { @Before public void setup() { - when(mAidlSession.getSession()).thenReturn(mSession); mAddedIds = new ArrayList<>(); + + when(mAidlSession.getSession()).thenReturn(mSession); } - @Ignore("TODO(b/229015801): verify cleanup behavior") @Test public void removesUnknownTemplate() throws Exception { mClient = createClient(); + final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class); final List<Fingerprint> templates = List.of( new Fingerprint("one", 1, 1), new Fingerprint("two", 2, 1) @@ -108,8 +109,8 @@ public class FingerprintInternalCleanupClientTest { mClient.getCurrentRemoveClient().onRemoved(templates.get(i), 0); } + verify(mSession).enumerateEnrollments(); assertThat(mAddedIds).isEmpty(); - final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class); verify(mSession, times(2)).removeEnrollments(captor.capture()); assertThat(captor.getAllValues().stream() .flatMap(x -> Arrays.stream(x).boxed()) @@ -132,13 +133,15 @@ public class FingerprintInternalCleanupClientTest { mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i); } + verify(mSession).enumerateEnrollments(); assertThat(mAddedIds).containsExactly(1, 2); verify(mSession, never()).removeEnrollments(any()); verify(mCallback).onClientFinished(eq(mClient), eq(true)); } @Test - public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled() { + public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled() + throws RemoteException { mClient = createClient(); final List<Fingerprint> templates = List.of( @@ -150,6 +153,8 @@ public class FingerprintInternalCleanupClientTest { for (int i = templates.size() - 1; i >= 0; i--) { mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i); } + + verify(mSession).enumerateEnrollments(); // The first template is removed after enumeration assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(2); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java new file mode 100644 index 000000000000..723f916f99c8 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.fingerprint.Fingerprint; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Presubmit +@SmallTest +public class FingerprintInternalEnumerateClientTest { + private static final String TAG = "FingerprintInternalEnumerateClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + @Mock + private BiometricUtils<Fingerprint> mBiometricUtils; + + private FingerprintInternalEnumerateClient mClient; + + @Before + public void setUp() { + when(mAidlSession.getSession()).thenReturn(mSession); + + List<Fingerprint> enrolled = new ArrayList<>(); + enrolled.add(new Fingerprint("one", 1, 1)); + mClient = new FingerprintInternalEnumerateClient(mContext, () -> mAidlSession, mToken, + USER_ID, TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger, + mBiometricContext); + } + + @Test + public void internalEnumerate_unknownTemplates() throws RemoteException { + doAnswer(invocation -> { + mClient.onEnumerationResult(new Fingerprint("two", 2, 1), 1); + mClient.onEnumerationResult(new Fingerprint("three", 3, 1), 0); + return null; + }).when(mSession).enumerateEnrollments(); + mClient.start(mCallback); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().stream() + .flatMap(x -> Stream.of(x.getBiometricId())) + .collect(Collectors.toList())).containsExactly(2, 3); + verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, 1); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void internalEnumerate_noUnknownTemplates() throws RemoteException { + doAnswer(invocation -> { + mClient.onEnumerationResult(new Fingerprint("one", 1, 1), 0); + return null; + }).when(mSession).enumerateEnrollments(); + mClient.start(mCallback); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); + verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt()); + verify(mCallback).onClientFinished(mClient, true); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java new file mode 100644 index 000000000000..64f07e20e958 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.fingerprint.Fingerprint; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Presubmit +@SmallTest +public class FingerprintRemovalClientTest { + private static final String TAG = "FingerprintRemovalClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallbackConverter mListener; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + @Mock + private BiometricUtils<Fingerprint> mBiometricUtils; + @Mock + private Map<Integer, Long> mAuthenticatorIds; + + private FingerprintRemovalClient mClient; + private int[] mBiometricIds = new int[]{1, 2}; + + @Before + public void setUp() { + when(mAidlSession.getSession()).thenReturn(mSession); + + mClient = new FingerprintRemovalClient(mContext, () -> mAidlSession, mToken, mListener, + mBiometricIds, USER_ID, TAG, mBiometricUtils, SENSOR_ID, + mBiometricLogger, mBiometricContext, mAuthenticatorIds); + } + + @Test + public void removalMultipleFingerprints() throws RemoteException { + when(mBiometricUtils.getBiometricsForUser(any(), anyInt())).thenReturn( + List.of(new Fingerprint("three", 3, 1))); + doAnswer(invocation -> { + mClient.onRemoved(new Fingerprint("one", 1, 1), 1); + mClient.onRemoved(new Fingerprint("two", 2, 1), 0); + return null; + }).when(mSession).removeEnrollments(mBiometricIds); + mClient.start(mCallback); + + verify(mSession).removeEnrollments(mBiometricIds); + verify(mBiometricUtils, times(2)).removeBiometricForUser(eq(mContext), + eq(USER_ID), anyInt()); + verifyNoMoreInteractions(mAuthenticatorIds); + verify(mListener, times(2)).onRemoved(any(), anyInt()); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void removeFingerprint_nullIdentifier() throws RemoteException { + doAnswer(invocation -> { + mClient.onRemoved(null, 0); + return null; + }).when(mSession).removeEnrollments(mBiometricIds); + mClient.start(mCallback); + + verify(mSession).removeEnrollments(mBiometricIds); + verify(mListener).onError(anyInt(), anyInt(), anyInt(), anyInt()); + verify(mCallback).onClientFinished(mClient, false); + } + + @Test + public void removeFingerprints_noFingerprintEnrolled() throws RemoteException { + doAnswer(invocation -> { + mClient.onRemoved(new Fingerprint("one", 1, 1), 1); + mClient.onRemoved(new Fingerprint("two", 2, 1), 0); + return null; + }).when(mSession).removeEnrollments(mBiometricIds); + when(mBiometricUtils.getBiometricsForUser(any(), anyInt())).thenReturn(new ArrayList<>()); + + mClient.start(mCallback); + + verify(mSession).removeEnrollments(mBiometricIds); + verify(mBiometricUtils, times(2)).removeBiometricForUser(eq(mContext), + eq(USER_ID), anyInt()); + verify(mAuthenticatorIds).put(USER_ID, 0L); + verify(mListener, times(2)).onRemoved(any(), anyInt()); + verify(mCallback).onClientFinished(mClient, true); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java new file mode 100644 index 000000000000..a4746dea7ac5 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.BiometricManager; +import android.hardware.biometrics.fingerprint.ISession; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.LockoutResetDispatcher; +import com.android.server.biometrics.sensors.LockoutTracker; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FingerprintResetLockoutClientTest { + private static final String TAG = "FingerprintResetLockoutClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + @Mock + private LockoutTracker mLockoutTracker; + @Mock + private LockoutResetDispatcher mLockoutResetDispatcher; + @Mock + private AuthSessionCoordinator mAuthSessionCoordinator; + + private FingerprintResetLockoutClient mClient; + + @Before + public void setUp() { + when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator); + when(mAidlSession.getSession()).thenReturn(mSession); + + mClient = new FingerprintResetLockoutClient(mContext, () -> mAidlSession, USER_ID, TAG, + SENSOR_ID, mBiometricLogger, mBiometricContext, new byte[69], + mLockoutTracker, mLockoutResetDispatcher, + BiometricManager.Authenticators.BIOMETRIC_STRONG); + } + + @Test + public void resetLockout_onLockoutCleared() throws RemoteException { + doAnswer(invocation -> { + mClient.onLockoutCleared(); + return null; + }).when(mSession).resetLockout(any()); + mClient.start(mCallback); + + verify(mSession).resetLockout(any()); + verify(mLockoutTracker).setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_NONE); + verify(mLockoutTracker).resetFailedAttemptsForUser(true, USER_ID); + verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(SENSOR_ID); + verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), + eq(BiometricManager.Authenticators.BIOMETRIC_STRONG), anyLong()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java new file mode 100644 index 000000000000..f19b2f7da8a5 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.fingerprint.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FingerprintRevokeChallengeClientTest { + private static final String TAG = "FingerprintRevokeChallengeClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + private static final long CHALLENGE = 200; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + + private FingerprintRevokeChallengeClient mClient; + + @Before + public void setUp() { + when(mAidlSession.getSession()).thenReturn(mSession); + + mClient = new FingerprintRevokeChallengeClient(mContext, () -> mAidlSession, mToken, + USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, CHALLENGE); + } + + @Test + public void revokeChallenge_sameChallenge() throws RemoteException { + doAnswer(invocation -> { + mClient.onChallengeRevoked(CHALLENGE); + return null; + }).when(mSession).revokeChallenge(CHALLENGE); + mClient.start(mCallback); + + verify(mSession).revokeChallenge(CHALLENGE); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void revokeChallenge_differentChallenge() throws RemoteException { + doAnswer(invocation -> { + mClient.onChallengeRevoked(CHALLENGE + 1); + return null; + }).when(mSession).revokeChallenge(CHALLENGE); + mClient.start(mCallback); + + verify(mSession).revokeChallenge(CHALLENGE); + verify(mCallback).onClientFinished(mClient, false); + } +} 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 15d7601dde34..410260074fbd 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 @@ -75,7 +75,7 @@ public class SensorTest { @Mock private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback; @Mock - private Sensor.HalSessionCallback.Callback mHalSessionCallback; + private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback; @Mock private LockoutResetDispatcher mLockoutResetDispatcher; @Mock @@ -97,7 +97,7 @@ public class SensorTest { private final LockoutCache mLockoutCache = new LockoutCache(); private UserAwareBiometricScheduler mScheduler; - private Sensor.HalSessionCallback mHalCallback; + private AidlResponseHandler mHalCallback; @Before public void setUp() { @@ -113,10 +113,9 @@ public class SensorTest { mBiometricService, () -> USER_ID, mUserSwitchCallback); - mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()), - TAG, mScheduler, SENSOR_ID, - USER_ID, mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, - mHalSessionCallback); + mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID, + mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, + mHardwareUnavailableCallback); } @Test diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java new file mode 100644 index 000000000000..b78ba82bd8fe --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.hidl; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; +import android.hardware.keymaster.HardwareAuthToken; +import android.hardware.keymaster.Timestamp; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class AidlToHidlAdapterTest { + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private IBiometricsFingerprint mSession; + @Mock + private AidlResponseHandler mAidlResponseHandler; + @Mock + private HardwareAuthToken mHardwareAuthToken; + + private final long mChallenge = 100L; + private final int mUserId = 0; + private AidlToHidlAdapter mAidlToHidlAdapter; + + @Before + public void setUp() { + mAidlToHidlAdapter = new AidlToHidlAdapter(() -> mSession, mUserId, + mAidlResponseHandler); + mHardwareAuthToken.timestamp = new Timestamp(); + mHardwareAuthToken.mac = new byte[10]; + } + + @Test + public void testGenerateChallenge() throws RemoteException { + when(mSession.preEnroll()).thenReturn(mChallenge); + mAidlToHidlAdapter.generateChallenge(); + + verify(mSession).preEnroll(); + verify(mAidlResponseHandler).onChallengeGenerated(mChallenge); + } + + @Test + public void testRevokeChallenge() throws RemoteException { + mAidlToHidlAdapter.revokeChallenge(mChallenge); + + verify(mSession).postEnroll(); + verify(mAidlResponseHandler).onChallengeRevoked(0L); + } + + @Test + public void testEnroll() throws RemoteException { + final ICancellationSignal cancellationSignal = + mAidlToHidlAdapter.enroll(mHardwareAuthToken); + + verify(mSession).enroll(any(), anyInt(), eq(AidlToHidlAdapter.ENROLL_TIMEOUT_SEC)); + + cancellationSignal.cancel(); + + verify(mSession).cancel(); + } + + @Test + public void testAuthenticate() throws RemoteException { + final int operationId = 2; + final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId); + + verify(mSession).authenticate(operationId, mUserId); + + cancellationSignal.cancel(); + + verify(mSession).cancel(); + } + + @Test + public void testDetectInteraction() throws RemoteException { + final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction(); + + verify(mSession).authenticate(0 /* operationId */, mUserId); + + cancellationSignal.cancel(); + + verify(mSession).cancel(); + } + + @Test + public void testEnumerateEnrollments() throws RemoteException { + mAidlToHidlAdapter.enumerateEnrollments(); + + verify(mSession).enumerate(); + } + + @Test + public void testRemoveEnrollment() throws RemoteException { + final int[] enrollmentIds = new int[]{1}; + mAidlToHidlAdapter.removeEnrollments(enrollmentIds); + + verify(mSession).remove(mUserId, enrollmentIds[0]); + } + + @Test + public void testRemoveMultipleEnrollments() throws RemoteException { + final int[] enrollmentIds = new int[]{1, 2}; + mAidlToHidlAdapter.removeEnrollments(enrollmentIds); + + verify(mSession).remove(mUserId, 0); + } + + @Test + public void testResetLockout() throws RemoteException { + mAidlToHidlAdapter.resetLockout(mHardwareAuthToken); + + verify(mAidlResponseHandler).onLockoutCleared(); + } +} diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java index 79348d104745..ae7c2a99b808 100644 --- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java +++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java @@ -247,7 +247,7 @@ public class GraphicsActivity extends Activity { int rc = 0; try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) { - transaction.setFrameRateCategory(mSurfaceControl, category); + transaction.setFrameRateCategory(mSurfaceControl, category, false); transaction.apply(); } return rc; |