diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 2d164f8915905cd067f5492bbf70d4709f3f5ecb..a1375d79846df1adab1fe902ddedbc64268030cc 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -36,7 +36,9 @@ aconfig_srcjars = [ ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}", ":sdk_sandbox_flags_lib{.generated_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}", @@ -45,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 { @@ -332,6 +335,19 @@ java_aconfig_library { } +// SQLite +aconfig_declarations { + name: "android.database.sqlite-aconfig", + package: "android.database.sqlite", + srcs: ["core/java/android/database/sqlite/*.aconfig"], +} + +java_aconfig_library { + name: "android.database.sqlite-aconfig-java", + aconfig_declarations: "android.database.sqlite-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Biometrics aconfig_declarations { name: "android.hardware.biometrics.flags-aconfig", @@ -352,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", @@ -471,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 4e3cb7d83451f60d73513245108fcc46ad829705..3e835b8ad429ff58af48f309fbe7875952b23712 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 3cbee5d07bb923dae2e7588d51b31f92beb5aff3..384a480af4e95d95b10c598a24679ea81fff3a06 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"; @@ -834,13 +827,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; @@ -3992,63 +3931,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 @@ -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/StubLibraries.bp b/api/StubLibraries.bp index 2d9c988556ec7c724d91356083f0cb49efb0297d..fa4bc0f98fa5a7a657fca818d0939cbbc406cfe7 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -438,6 +438,26 @@ java_api_library { full_api_surface_stub: "android_module_lib_stubs_current_full.from-text", } +// This module generates a stub jar that is a union of the test and module lib +// non-updatable api contributions. Modules should not depend on the stub jar +// generated from this module, as this module is strictly used for hiddenapi only. +java_api_library { + name: "android-non-updatable.stubs.test_module_lib", + api_surface: "module_lib", + api_contributions: [ + "api-stubs-docs-non-updatable.api.contribution", + "system-api-stubs-docs-non-updatable.api.contribution", + "test-api-stubs-docs-non-updatable.api.contribution", + "module-lib-api-stubs-docs-non-updatable.api.contribution", + ], + defaults: ["android-non-updatable_from_text_defaults"], + full_api_surface_stub: "android_test_module_lib_stubs_current.from-text", + + // This module is only used for hiddenapi, and other modules should not + // depend on this module. + visibility: ["//visibility:private"], +} + java_defaults { name: "android_stubs_dists_default", dist: { @@ -756,6 +776,30 @@ java_api_library { visibility: ["//visibility:public"], } +java_api_library { + name: "android_test_module_lib_stubs_current.from-text", + api_surface: "module-lib", + defaults: [ + "android_stubs_current_contributions", + "android_system_stubs_current_contributions", + "android_test_stubs_current_contributions", + "android_module_lib_stubs_current_contributions", + ], + libs: [ + "android_module_lib_stubs_current_full.from-text", + "stub-annotations", + ], + api_contributions: [ + "test-api-stubs-docs-non-updatable.api.contribution", + ], + + // This module is only used to build android-non-updatable.stubs.test_module_lib + // and other modules should not depend on this module. + visibility: [ + "//visibility:private", + ], +} + java_api_library { name: "android_system_server_stubs_current.from-text", api_surface: "system-server", diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline index 1f023bd3952077dc9fb867b3fc93529c0f462193..c28480e4f11a576e4da2a4502a2490e439b65032 100644 --- a/api/javadoc-lint-baseline +++ b/api/javadoc-lint-baseline @@ -86,28 +86,12 @@ android/app/appsearch/SearchSpec.java:929: lint: Unresolved link/see tag "Featur android/app/job/JobParameters.java:128: lint: Unresolved link/see tag "android.app.job.JobInfo.Builder#setPeriodic(boolean) periodic jobs" in android.app.job.JobParameters [101] android/app/sdksandbox/AppOwnedSdkSandboxInterface.java:9: lint: Unresolved link/see tag "SdkSandboxController#getAppOwnedSdkSandboxInterfaces" in android.app.sdksandbox.AppOwnedSdkSandboxInterface [101] android/app/sdksandbox/SdkSandboxManager.java:112: lint: Unresolved link/see tag "AppOwnedSdkSandboxInterfaces" in android.app.sdksandbox.SdkSandboxManager [101] -android/companion/CompanionDeviceService.java:273: lint: Unresolved link/see tag "android.companion.AssociationInfo#isSelfManaged() self-managed" in android.companion.CompanionDeviceService [101] -android/companion/CompanionDeviceService.java:282: lint: Unresolved link/see tag "android.companion.AssociationInfo#isSelfManaged() self-managed" in android.companion.CompanionDeviceService [101] -android/companion/virtual/VirtualDevice.java:15: lint: Unresolved link/see tag "android.companion.virtual.VirtualDeviceManager.VirtualDevice VirtualDeviceManager.VirtualDevice" in android.companion.virtual.VirtualDevice [101] -android/companion/virtual/VirtualDevice.java:70: lint: Unresolved link/see tag "android.companion.virtual.VirtualDeviceParams.Builder#setName(String)" in android.companion.virtual.VirtualDevice [101] android/content/AttributionSource.java:291: lint: Unresolved link/see tag "setNextAttributionSource" in android.content.AttributionSource.Builder [101] android/content/Context.java:2872: lint: Unresolved link/see tag "android.telephony.MmsManager" in android.content.Context [101] android/content/Intent.java:4734: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101] android/content/Intent.java:4760: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101] android/content/Intent.java:4778: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101] android/content/Intent.java:4802: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101] -android/credentials/CreateCredentialException.java:22: lint: Unresolved link/see tag "android.credentials.CredentialManager#createCredential(android.credentials.CreateCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#createCredential(CreateCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.CreateCredentialException [101] -android/credentials/CreateCredentialException.java:101: lint: Unresolved link/see tag "android.credentials.CredentialManager#createCredential(android.credentials.CreateCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#createCredential(CreateCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.CreateCredentialException [101] -android/credentials/CreateCredentialRequest.java:107: lint: Unresolved link/see tag "androidx.credentials.CreateCredentialRequest" in android.credentials.CreateCredentialRequest.Builder [101] -android/credentials/CredentialDescription.java:89: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mSupportedElementKeys CredentialDescription#mSupportedElementKeys" in android.credentials.CredentialDescription [101] -android/credentials/CredentialDescription.java:89: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mType CredentialDescription#mType" in android.credentials.CredentialDescription [101] -android/credentials/CredentialDescription.java:101: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mSupportedElementKeys CredentialDescription#mSupportedElementKeys" in android.credentials.CredentialDescription [101] -android/credentials/CredentialDescription.java:101: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mType CredentialDescription#mType" in android.credentials.CredentialDescription [101] -android/credentials/GetCredentialException.java:22: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.GetCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.GetCredentialException [101] -android/credentials/GetCredentialException.java:103: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.GetCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver)" in android.credentials.GetCredentialException [101] -android/credentials/PrepareGetCredentialResponse.java:20: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse [101] -android/credentials/PrepareGetCredentialResponse.java:68: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse [101] -android/credentials/PrepareGetCredentialResponse.java:83: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle [101] android/graphics/Paint.java:838: lint: Unresolved link/see tag "android.annotation.ColorLong ColorLong" in android.graphics.Paint [101] android/graphics/text/LineBreaker.java:246: lint: Unresolved link/see tag "StaticLayout.Builder#setUseBoundsForWidth(boolean)" in android.graphics.text.LineBreaker.Builder [101] android/hardware/camera2/CameraCharacteristics.java:2169: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101] @@ -158,8 +142,6 @@ android/media/AudioManager.java:287: lint: Unresolved link/see tag "android.medi android/media/AudioManager.java:311: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101] android/media/AudioManager.java:313: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101] android/media/AudioMetadata.java:118: lint: Unresolved link/see tag "android.media.AudioPresentation.ContentClassifier One of {@link android.media.AudioPresentation#CONTENT_UNKNOWN AudioPresentation#CONTENT_UNKNOWN}, {@link android.media.AudioPresentation#CONTENT_MAIN AudioPresentation#CONTENT_MAIN}, {@link android.media.AudioPresentation#CONTENT_MUSIC_AND_EFFECTS AudioPresentation#CONTENT_MUSIC_AND_EFFECTS}, {@link android.media.AudioPresentation#CONTENT_VISUALLY_IMPAIRED AudioPresentation#CONTENT_VISUALLY_IMPAIRED}, {@link android.media.AudioPresentation#CONTENT_HEARING_IMPAIRED AudioPresentation#CONTENT_HEARING_IMPAIRED}, {@link android.media.AudioPresentation#CONTENT_DIALOG AudioPresentation#CONTENT_DIALOG}, {@link android.media.AudioPresentation#CONTENT_COMMENTARY AudioPresentation#CONTENT_COMMENTARY}, {@link android.media.AudioPresentation#CONTENT_EMERGENCY AudioPresentation#CONTENT_EMERGENCY}, {@link android.media.AudioPresentation#CONTENT_VOICEOVER AudioPresentation#CONTENT_VOICEOVER}." in android.media.AudioMetadata.Format [101] -android/media/MediaRouter2.java:162: lint: Unresolved link/see tag "#getInstance(android.content.Context,java.lang.String)" in android.media.MediaRouter2 [101] -android/media/midi/MidiUmpDeviceService.java:-1: lint: Unresolved link/see tag "#MidiDeviceService" in android.media.midi.MidiUmpDeviceService [101] android/media/tv/SectionRequest.java:44: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.SectionRequest [101] android/media/tv/SectionResponse.java:39: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.SectionResponse [101] android/media/tv/TableRequest.java:48: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.TableRequest [101] @@ -205,17 +187,6 @@ android/provider/Settings.java:2195: lint: Unresolved link/see tag "android.app. android/security/KeyStoreException.java:27: lint: Unresolved link/see tag "android.security.KeyStoreException.PublicErrorCode PublicErrorCode" in android.security.KeyStoreException [101] android/service/autofill/FillResponse.java:86: lint: Unresolved link/see tag "setFieldClassificationIds" in android.service.autofill.FillResponse.Builder [101] android/service/autofill/SaveInfo.java:623: lint: Unresolved link/see tag "FillRequest.getHints()" in android.service.autofill.SaveInfo.Builder [101] -android/service/credentials/Action.java:3: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.Action [101] -android/service/credentials/Action.java:3: lint: Unresolved link/see tag "androidx.credentials.provider.Action" in android.service.credentials.Action [101] -android/service/credentials/BeginCreateCredentialResponse.java:85: lint: Unresolved link/see tag "Manifest.permission.PROVIDE_REMOTE_CREDENTIALS" in android.service.credentials.BeginCreateCredentialResponse.Builder [101] -android/service/credentials/BeginGetCredentialResponse.java:80: lint: Unresolved link/see tag "Manifest.permission.PROVIDE_REMOTE_CREDENTIALS" in android.service.credentials.BeginGetCredentialResponse.Builder [101] -android/service/credentials/CallingAppInfo.java:73: lint: Unresolved link/see tag "android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN" in android.service.credentials.CallingAppInfo [101] -android/service/credentials/CreateEntry.java:6: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.CreateEntry [101] -android/service/credentials/CreateEntry.java:6: lint: Unresolved link/see tag "androidx.credentials.provider.CreateEntry" in android.service.credentials.CreateEntry [101] -android/service/credentials/CredentialEntry.java:11: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.CredentialEntry [101] -android/service/credentials/CredentialEntry.java:11: lint: Unresolved link/see tag "androidx.credentials.provider.CredentialEntry" in android.service.credentials.CredentialEntry [101] -android/service/credentials/RemoteEntry.java:13: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.RemoteEntry [101] -android/service/credentials/RemoteEntry.java:13: lint: Unresolved link/see tag "androidx.credentials.provider.RemoteEntry" in android.service.credentials.RemoteEntry [101] android/service/notification/NotificationListenerService.java:417: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService notification assistant" in android.service.notification.NotificationListenerService [101] android/service/notification/NotificationListenerService.java:435: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService notification assistant" in android.service.notification.NotificationListenerService [101] android/service/notification/NotificationListenerService.java:1155: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService NotificationAssistantService" in android.service.notification.NotificationListenerService.Ranking [101] @@ -224,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] @@ -261,21 +206,11 @@ 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/companion/virtual/VirtualDeviceImpl.java:134: lint: Unresolved link/see tag "DisplayManager" in android [101] -com/android/server/companion/virtual/VirtualDeviceImpl.java:134: lint: Unresolved link/see tag "VirtualDeviceManager.VirtualDevice" in android [101] com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "IdentifierType#DAB_SID_EXT" in android [101] com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT" in android [101] com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "RadioTuner" in android [101] -com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "ComponentName" in android [101] -com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "IllegalArgumentException" in android [101] -com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "MediaSession#setMediaButtonBroadcastReceiver(ComponentName)" in android [101] -com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "IllegalArgumentException" in android [101] -com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "MediaSession#setMediaButtonReceiver(PendingIntent)" in android [101] -com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "PendingIntent" in android [101] com/android/server/pm/PackageInstallerSession.java:313: lint: Unresolved link/see tag "Build.VERSION_CODES#S API 31" in android [101] com/android/server/pm/PackageInstallerSession.java:313: lint: Unresolved link/see tag "PackageInstaller.SessionParams#setRequireUserAction" in android [101] com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "#requestUserPreapproval(PreapprovalDetails, IntentSender)" in android [101] @@ -284,12 +219,7 @@ com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/se com/android/server/pm/PackageInstallerSession.java:358: lint: Unresolved link/see tag "IntentSender" in android [101] com/android/server/devicepolicy/DevicePolicyManagerService.java:860: lint: Unresolved link/see tag "android.security.IKeyChainService#setGrant" in android [101] -android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:-4: lint: Invalid tag: @Override [131] -android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:-1: lint: Invalid tag: @Override [131] -android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:2: lint: Invalid tag: @Override [131] 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 f908d9546a34d62d7b4cb9ee43fdfb92e848f6e0..6c0fccc871984a5d12af23790bdb0bda0de10583 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -14352,14 +14352,14 @@ package android.database.sqlite { public final class SQLiteDatabase extends android.database.sqlite.SQLiteClosable { method public void beginTransaction(); method public void beginTransactionNonExclusive(); - method public void beginTransactionReadOnly(); + method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public void beginTransactionReadOnly(); method public void beginTransactionWithListener(@Nullable android.database.sqlite.SQLiteTransactionListener); method public void beginTransactionWithListenerNonExclusive(@Nullable android.database.sqlite.SQLiteTransactionListener); - method public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener); + method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener); method public android.database.sqlite.SQLiteStatement compileStatement(String) throws android.database.SQLException; method @NonNull public static android.database.sqlite.SQLiteDatabase create(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory); method @NonNull public static android.database.sqlite.SQLiteDatabase createInMemory(@NonNull android.database.sqlite.SQLiteDatabase.OpenParams); - method @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String); + method @FlaggedApi("android.database.sqlite.sqlite_apis_15") @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String); method public int delete(@NonNull String, @Nullable String, @Nullable String[]); method public static boolean deleteDatabase(@NonNull java.io.File); method public void disableWriteAheadLogging(); @@ -14370,13 +14370,13 @@ package android.database.sqlite { method public void execSQL(@NonNull String, @NonNull Object[]) throws android.database.SQLException; method public static String findEditTable(String); method public java.util.List<android.util.Pair<java.lang.String,java.lang.String>> getAttachedDbs(); - method public long getLastChangedRowCount(); - method public long getLastInsertRowId(); + method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getLastChangedRowCount(); + method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getLastInsertRowId(); method public long getMaximumSize(); method public long getPageSize(); method public String getPath(); method @Deprecated public java.util.Map<java.lang.String,java.lang.String> getSyncedTables(); - method public long getTotalChangedRowCount(); + method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getTotalChangedRowCount(); method public int getVersion(); method public boolean inTransaction(); method public long insert(@NonNull String, @Nullable String, @Nullable android.content.ContentValues); @@ -14598,7 +14598,7 @@ package android.database.sqlite { method public int update(@NonNull android.database.sqlite.SQLiteDatabase, @NonNull android.content.ContentValues, @Nullable String, @Nullable String[]); } - public final class SQLiteRawStatement implements java.io.Closeable { + @FlaggedApi("android.database.sqlite.sqlite_apis_15") public final class SQLiteRawStatement implements java.io.Closeable { method public void bindBlob(int, @NonNull byte[]) throws android.database.sqlite.SQLiteException; method public void bindBlob(int, @NonNull byte[], int, int) throws android.database.sqlite.SQLiteException; method public void bindDouble(int, double) throws android.database.sqlite.SQLiteException; @@ -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(); @@ -33094,6 +33094,22 @@ package android.os { method public void onStateChanged(boolean); } + @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public final class PowerMonitor implements android.os.Parcelable { + method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public int describeContents(); + method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") @NonNull public String getName(); + method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public int getType(); + method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void writeToParcel(@NonNull android.os.Parcel, int); + field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") @NonNull public static final android.os.Parcelable.Creator<android.os.PowerMonitor> CREATOR; + field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int POWER_MONITOR_TYPE_CONSUMER = 0; // 0x0 + field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int POWER_MONITOR_TYPE_MEASUREMENT = 1; // 0x1 + } + + @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public final class PowerMonitorReadings { + method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public long getConsumedEnergy(@NonNull android.os.PowerMonitor); + method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public long getTimestamp(@NonNull android.os.PowerMonitor); + field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int ENERGY_UNAVAILABLE = -1; // 0xffffffff + } + public class Process { ctor public Process(); method public static final long getElapsedCpuTime(); @@ -33656,6 +33672,8 @@ package android.os.health { } public class SystemHealthManager { + method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getPowerMonitorReadings(@NonNull java.util.List<android.os.PowerMonitor>, @Nullable android.os.Handler, @NonNull java.util.function.Consumer<android.os.PowerMonitorReadings>, @NonNull java.util.function.Consumer<java.lang.RuntimeException>); + method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getSupportedPowerMonitors(@Nullable android.os.Handler, @NonNull java.util.function.Consumer<java.util.List<android.os.PowerMonitor>>); method public android.os.health.HealthStats takeMyUidSnapshot(); method public android.os.health.HealthStats takeUidSnapshot(int); method public android.os.health.HealthStats[] takeUidSnapshots(int[]); @@ -53129,6 +53147,7 @@ package android.view { method @Nullable public abstract android.view.View getCurrentFocus(); method @NonNull public abstract android.view.View getDecorView(); method public static int getDefaultFeatures(android.content.Context); + method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public float getDesiredHdrHeadroom(); method public android.transition.Transition getEnterTransition(); method public android.transition.Transition getExitTransition(); method protected final int getFeatures(); @@ -53198,6 +53217,7 @@ package android.view { method public abstract void setDecorCaptionShade(int); method public void setDecorFitsSystemWindows(boolean); method protected void setDefaultWindowFormat(int); + method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0) float); method public void setDimAmount(float); method public void setElevation(float); method public void setEnterTransition(android.transition.Transition); @@ -53547,6 +53567,7 @@ package android.view { method public int describeContents(); method public int getBlurBehindRadius(); method public int getColorMode(); + method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public float getDesiredHdrHeadroom(); method public int getFitInsetsSides(); method public int getFitInsetsTypes(); method public final CharSequence getTitle(); @@ -53556,6 +53577,7 @@ package android.view { method public void setBlurBehindRadius(@IntRange(from=0) int); method public void setCanPlayMoveAnimation(boolean); method public void setColorMode(int); + method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0f) float); method public void setFitInsetsIgnoringVisibility(boolean); method public void setFitInsetsSides(int); method public void setFitInsetsTypes(int); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 7dcc7b2cab13f78914c0f0be254518f029223965..a1da9e02f23d0744427dd0ea3cf664a2fb142968 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 17637df90b99d914aa32258072cf0087da2270d4..ecbc9b1d52c2cd3429a7cc2eecce0cd83a148b8b 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/app/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java new file mode 100644 index 0000000000000000000000000000000000000000..98281338872bcfb00238270bfc48a0f951539d5a --- /dev/null +++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java @@ -0,0 +1,193 @@ +/* + * 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 android.app.servertransaction; + +import static android.view.Display.INVALID_DISPLAY; + +import static java.util.Objects.requireNonNull; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ClientTransactionHandler; +import android.os.Parcel; +import android.os.RemoteException; +import android.util.MergedConfiguration; +import android.view.IWindow; +import android.view.InsetsState; +import android.window.ClientWindowFrames; + +import java.util.Objects; + +/** + * Message to deliver window resize info. + * @hide + */ +public class WindowStateResizeItem extends ClientTransactionItem { + + private IWindow mWindow; + private ClientWindowFrames mFrames; + private boolean mReportDraw; + private MergedConfiguration mConfiguration; + private InsetsState mInsetsState; + private boolean mForceLayout; + private boolean mAlwaysConsumeSystemBars; + private int mDisplayId; + private int mSyncSeqId; + private boolean mDragResizing; + + @Override + public void execute(@NonNull ClientTransactionHandler client, + @NonNull PendingTransactionActions pendingActions) { + try { + mWindow.resized(mFrames, mReportDraw, mConfiguration, mInsetsState, mForceLayout, + mAlwaysConsumeSystemBars, mDisplayId, mSyncSeqId, mDragResizing); + } catch (RemoteException e) { + // Should be a local call. + throw new RuntimeException(e); + } + } + + // ObjectPoolItem implementation + + private WindowStateResizeItem() {} + + /** Obtains an instance initialized with provided params. */ + public static WindowStateResizeItem obtain(@NonNull IWindow window, + @NonNull ClientWindowFrames frames, boolean reportDraw, + @NonNull MergedConfiguration configuration, @NonNull InsetsState insetsState, + boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, + boolean dragResizing) { + WindowStateResizeItem instance = + ObjectPool.obtain(WindowStateResizeItem.class); + if (instance == null) { + instance = new WindowStateResizeItem(); + } + instance.mWindow = requireNonNull(window); + instance.mFrames = requireNonNull(frames); + instance.mReportDraw = reportDraw; + instance.mConfiguration = requireNonNull(configuration); + instance.mInsetsState = requireNonNull(insetsState); + instance.mForceLayout = forceLayout; + instance.mAlwaysConsumeSystemBars = alwaysConsumeSystemBars; + instance.mDisplayId = displayId; + instance.mSyncSeqId = syncSeqId; + instance.mDragResizing = dragResizing; + + return instance; + } + + @Override + public void recycle() { + mWindow = null; + mFrames = null; + mReportDraw = false; + mConfiguration = null; + mInsetsState = null; + mForceLayout = false; + mAlwaysConsumeSystemBars = false; + mDisplayId = INVALID_DISPLAY; + mSyncSeqId = -1; + mDragResizing = false; + ObjectPool.recycle(this); + } + + // Parcelable implementation + + /** Writes to Parcel. */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStrongBinder(mWindow.asBinder()); + dest.writeTypedObject(mFrames, flags); + dest.writeBoolean(mReportDraw); + dest.writeTypedObject(mConfiguration, flags); + dest.writeTypedObject(mInsetsState, flags); + dest.writeBoolean(mForceLayout); + dest.writeBoolean(mAlwaysConsumeSystemBars); + dest.writeInt(mDisplayId); + dest.writeInt(mSyncSeqId); + dest.writeBoolean(mDragResizing); + } + + /** Reads from Parcel. */ + private WindowStateResizeItem(@NonNull Parcel in) { + mWindow = IWindow.Stub.asInterface(in.readStrongBinder()); + mFrames = in.readTypedObject(ClientWindowFrames.CREATOR); + mReportDraw = in.readBoolean(); + mConfiguration = in.readTypedObject(MergedConfiguration.CREATOR); + mInsetsState = in.readTypedObject(InsetsState.CREATOR); + mForceLayout = in.readBoolean(); + mAlwaysConsumeSystemBars = in.readBoolean(); + mDisplayId = in.readInt(); + mSyncSeqId = in.readInt(); + mDragResizing = in.readBoolean(); + } + + public static final @NonNull Creator<WindowStateResizeItem> CREATOR = new Creator<>() { + public WindowStateResizeItem createFromParcel(@NonNull Parcel in) { + return new WindowStateResizeItem(in); + } + + public WindowStateResizeItem[] newArray(int size) { + return new WindowStateResizeItem[size]; + } + }; + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final WindowStateResizeItem other = (WindowStateResizeItem) o; + return Objects.equals(mWindow, other.mWindow) + && Objects.equals(mFrames, other.mFrames) + && mReportDraw == other.mReportDraw + && Objects.equals(mConfiguration, other.mConfiguration) + && Objects.equals(mInsetsState, other.mInsetsState) + && mForceLayout == other.mForceLayout + && mAlwaysConsumeSystemBars == other.mAlwaysConsumeSystemBars + && mDisplayId == other.mDisplayId + && mSyncSeqId == other.mSyncSeqId + && mDragResizing == other.mDragResizing; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + Objects.hashCode(mWindow); + result = 31 * result + Objects.hashCode(mFrames); + result = 31 * result + (mReportDraw ? 1 : 0); + result = 31 * result + Objects.hashCode(mConfiguration); + result = 31 * result + Objects.hashCode(mInsetsState); + result = 31 * result + (mForceLayout ? 1 : 0); + result = 31 * result + (mAlwaysConsumeSystemBars ? 1 : 0); + result = 31 * result + mDisplayId; + result = 31 * result + mSyncSeqId; + result = 31 * result + (mDragResizing ? 1 : 0); + return result; + } + + @Override + public String toString() { + return "WindowStateResizeItem{window=" + mWindow + + ", reportDrawn=" + mReportDraw + + ", configuration=" + mConfiguration + + "}"; + } +} diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java index 03e75e9fc483f9e458bd93a1795782c4a80abfa5..570ecaa47b4e49539fe389399bae7424bf5b74fa 100644 --- a/core/java/android/companion/CompanionDeviceService.java +++ b/core/java/android/companion/CompanionDeviceService.java @@ -161,16 +161,16 @@ public abstract class CompanionDeviceService extends Service { public static final int DEVICE_EVENT_BT_DISCONNECTED = 3; /** - * A companion app for a {@link AssociationInfo#isSelfManaged() self-managed} device will - * receive the callback {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a - * device has appeared on its own. + * A companion app for a self-managed device will receive the callback + * {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has appeared on its + * own. */ public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4; /** - * A companion app for a {@link AssociationInfo#isSelfManaged() self-managed} device will - * receive the callback {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a - * device has disappeared on its own. + * A companion app for a self-managed device will receive the callback + * {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has disappeared on + * its own. */ public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5; diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java index 0af4c92e0b6387fa70cd9c0060fe213f73f3d68b..93a3e782288853bcbbd42019cb534eac5c9cfc33 100644 --- a/core/java/android/companion/virtual/VirtualDevice.java +++ b/core/java/android/companion/virtual/VirtualDevice.java @@ -33,9 +33,6 @@ import android.os.RemoteException; * * <p>Read-only device representation exposing the properties of an existing virtual device. * - * <p class="note">Not to be confused with {@link VirtualDeviceManager.VirtualDevice}, which is used - * by the virtual device creator and allows them to manage the device. - * * @see VirtualDeviceManager#registerVirtualDeviceListener */ public final class VirtualDevice implements Parcelable { @@ -120,8 +117,6 @@ public final class VirtualDevice implements Parcelable { /** * Returns the name of the virtual device (optionally) provided during its creation. - * - * @see VirtualDeviceParams.Builder#setName(String) */ public @Nullable String getName() { return mName; diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java index bf78dd09e7c2ca35cdeacddd70c5e5db4c1578ee..b9451a74f9e91043ed7b51e24c6650ec92791458 100644 --- a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java +++ b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java @@ -46,15 +46,15 @@ import java.util.concurrent.atomic.AtomicLong; * <pre> * VirtualSensorDirectChannelWriter writer = new VirtualSensorDirectChannelWriter(); * VirtualSensorDirectChannelCallback callback = new VirtualSensorDirectChannelCallback() { - * @Override + * {@literal @}Override * public void onDirectChannelCreated(int channelHandle, SharedMemory sharedMemory) { * writer.addChannel(channelHandle, sharedMemory); * } - * @Override + * {@literal @}Override * public void onDirectChannelDestroyed(int channelHandle); * writer.removeChannel(channelHandle); * } - * @Override + * {@literal @}Override * public void onDirectChannelConfigured(int channelHandle, VirtualSensor sensor, int rateLevel, * int reportToken) * if (!writer.configureChannel(channelHandle, sensor, rateLevel, reportToken)) { diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 45338bb2c0a2bf68be1f57ead88db4dd686bc812..4d5d05611d7a3927e4486e2e544f60ae96c9f57e 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/credentials/CreateCredentialException.java b/core/java/android/credentials/CreateCredentialException.java index c3440043a4c048cccb343f2e97346d0a28b8d93e..8f07d19a859201ab9ef3a113fe3750203f415b1c 100644 --- a/core/java/android/credentials/CreateCredentialException.java +++ b/core/java/android/credentials/CreateCredentialException.java @@ -18,7 +18,7 @@ package android.credentials; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.Activity; +import android.content.Context; import android.os.CancellationSignal; import android.os.OutcomeReceiver; @@ -28,8 +28,8 @@ import java.util.concurrent.Executor; /** * Represents an error encountered during the - * {@link CredentialManager#createCredential(CreateCredentialRequest, - * Activity, CancellationSignal, Executor, OutcomeReceiver)} operation. + * {@link CredentialManager#createCredential(Context, CreateCredentialRequest, + * CancellationSignal, Executor, OutcomeReceiver)} operation. */ public class CreateCredentialException extends Exception { /** @@ -41,7 +41,7 @@ public class CreateCredentialException extends Exception { /** * The error type value for when no create options are available from any provider(s), - * for the given {@link CredentialManager#createCredential(CreateCredentialRequest, Activity, + * for the given {@link CredentialManager#createCredential(Context, CreateCredentialRequest, * CancellationSignal, Executor, OutcomeReceiver)} request. */ @NonNull diff --git a/core/java/android/credentials/CredentialDescription.java b/core/java/android/credentials/CredentialDescription.java index db71624cbe894ff6072f14f00bf8afdd3c749c05..755e6590a9343ec5b23997a02cd72f47ed9f4a29 100644 --- a/core/java/android/credentials/CredentialDescription.java +++ b/core/java/android/credentials/CredentialDescription.java @@ -155,8 +155,7 @@ public final class CredentialDescription implements Parcelable { } /** - * {@link CredentialDescription#mType} and - * {@link CredentialDescription#mSupportedElementKeys} are enough for hashing. Constructor + * {@link #getType()} and {@link #getSupportedElementKeys()} are enough for hashing. Constructor * enforces {@link CredentialEntry} to have the same type and * {@link android.app.slice.Slice} contained by the entry can not be hashed. */ @@ -166,8 +165,7 @@ public final class CredentialDescription implements Parcelable { } /** - * {@link CredentialDescription#mType} and - * {@link CredentialDescription#mSupportedElementKeys} are enough for equality check. + * {@link #getType()} and {@link #getSupportedElementKeys()} are enough for equality check. */ @Override public boolean equals(Object obj) { diff --git a/core/java/android/credentials/GetCredentialException.java b/core/java/android/credentials/GetCredentialException.java index 720c53ba51cd1cfbe8278d1916f44544c6bafc64..0421d1f5aba08854a8b947d5be09e76139a441fd 100644 --- a/core/java/android/credentials/GetCredentialException.java +++ b/core/java/android/credentials/GetCredentialException.java @@ -18,7 +18,7 @@ package android.credentials; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.Activity; +import android.content.Context; import android.os.CancellationSignal; import android.os.OutcomeReceiver; @@ -28,8 +28,8 @@ import java.util.concurrent.Executor; /** * Represents an error encountered during the - * {@link CredentialManager#getCredential(GetCredentialRequest, - * Activity, CancellationSignal, Executor, OutcomeReceiver)} operation. + * {@link CredentialManager#getCredential(Context, GetCredentialRequest, + * CancellationSignal, Executor, OutcomeReceiver)} operation. */ public class GetCredentialException extends Exception { /** @@ -41,7 +41,7 @@ public class GetCredentialException extends Exception { /** * The error type value for when no credential is found available for the given {@link - * CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, + * CredentialManager#getCredential(Context, GetCredentialRequest, CancellationSignal, * Executor, OutcomeReceiver)} request. */ @NonNull diff --git a/core/java/android/credentials/PrepareGetCredentialResponse.java b/core/java/android/credentials/PrepareGetCredentialResponse.java index 056b18a51b6d010b37186b1cd5e001c7ee4e66ac..212f5716d041a2794c97c29aac7f8ff337811572 100644 --- a/core/java/android/credentials/PrepareGetCredentialResponse.java +++ b/core/java/android/credentials/PrepareGetCredentialResponse.java @@ -22,7 +22,6 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; -import android.app.Activity; import android.app.PendingIntent; import android.content.Context; import android.content.IntentSender; @@ -36,7 +35,7 @@ import java.util.concurrent.Executor; /** * A response object that prefetches user app credentials and provides metadata about them. It can * then be used to issue the full credential retrieval flow via the - * {@link CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, + * {@link CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal, * Executor, OutcomeReceiver)} method to perform the remaining flows such as consent collection * and credential selection, to officially retrieve a credential. */ @@ -44,7 +43,7 @@ public final class PrepareGetCredentialResponse { /** * A handle that represents a pending get-credential operation. Pass this handle to {@link - * CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, + * CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal, * Executor, OutcomeReceiver)} to perform the remaining flows to officially retrieve a * credential. */ @@ -144,7 +143,7 @@ public final class PrepareGetCredentialResponse { /** * Returns a handle that represents this pending get-credential operation. Pass this handle to - * {@link CredentialManager#getCredential(PendingGetCredentialHandle, Activity, + * {@link CredentialManager#getCredential(Context, PendingGetCredentialHandle, * CancellationSignal, Executor, OutcomeReceiver)} to perform the remaining flows to officially * retrieve a credential. */ diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 746f2f23fd5c829078c2bb2312b528c5cca37dd8..b003e75b4e50b698bc4a595096380d7a65591171 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -16,6 +16,7 @@ package android.database.sqlite; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -700,6 +701,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * } * </pre> */ + @FlaggedApi(Flags.FLAG_SQLITE_APIS_15) public void beginTransactionReadOnly() { beginTransactionWithListenerReadOnly(null); } @@ -783,6 +785,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * } * </pre> */ + @FlaggedApi(Flags.FLAG_SQLITE_APIS_15) public void beginTransactionWithListenerReadOnly( @Nullable SQLiteTransactionListener transactionListener) { beginTransaction(transactionListener, SQLiteSession.TRANSACTION_MODE_DEFERRED); @@ -2221,6 +2224,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * @throws IllegalStateException if a transaction is not in progress. * @throws SQLiteException if the SQL cannot be compiled. */ + @FlaggedApi(Flags.FLAG_SQLITE_APIS_15) @NonNull public SQLiteRawStatement createRawStatement(@NonNull String sql) { Objects.requireNonNull(sql); @@ -2240,6 +2244,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * @return The ROWID of the last row to be inserted under this connection. * @throws IllegalStateException if there is no current transaction. */ + @FlaggedApi(Flags.FLAG_SQLITE_APIS_15) public long getLastInsertRowId() { return getThreadSession().getLastInsertRowId(); } @@ -2253,6 +2258,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * @return The number of rows changed by the most recent sql statement * @throws IllegalStateException if there is no current transaction. */ + @FlaggedApi(Flags.FLAG_SQLITE_APIS_15) public long getLastChangedRowCount() { return getThreadSession().getLastChangedRowCount(); } @@ -2280,6 +2286,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * @return The number of rows changed on the current connection. * @throws IllegalStateException if there is no current transaction. */ + @FlaggedApi(Flags.FLAG_SQLITE_APIS_15) public long getTotalChangedRowCount() { return getThreadSession().getTotalChangedRowCount(); } diff --git a/core/java/android/database/sqlite/SQLiteRawStatement.java b/core/java/android/database/sqlite/SQLiteRawStatement.java index 165f1810c8943a3f3558a32ed472954ade8e4868..827420f96fedd448b7053d33da98e23e5438adf8 100644 --- a/core/java/android/database/sqlite/SQLiteRawStatement.java +++ b/core/java/android/database/sqlite/SQLiteRawStatement.java @@ -16,6 +16,7 @@ package android.database.sqlite; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -70,6 +71,7 @@ import java.util.Objects; * * @see <a href="http://sqlite.org/c3ref/stmt.html">sqlite3_stmt</a> */ +@FlaggedApi(Flags.FLAG_SQLITE_APIS_15) public final class SQLiteRawStatement implements Closeable { private static final String TAG = "SQLiteRawStatement"; diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig new file mode 100644 index 0000000000000000000000000000000000000000..564df039456b02195d2424bcb18ddd9085d48f5e --- /dev/null +++ b/core/java/android/database/sqlite/flags.aconfig @@ -0,0 +1,9 @@ +package: "android.database.sqlite" + +flag { + name: "sqlite_apis_15" + namespace: "system_performance" + is_fixed_read_only: true + description: "SQLite APIs held back for Android 15" + bug: "279043253" +} diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 82694ee3463b438add4c8bf545c276fad49fa332..9c05dfc94ad4f7050b2ec1b29983f67968ce4e2a 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -536,6 +536,24 @@ public class BiometricManager { } } + /** + * Listens for biometric prompt status, i.e., if it is being shown or idle. + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void registerBiometricPromptStatusListener( + IBiometricPromptStatusListener callback) { + if (mService != null) { + try { + mService.registerBiometricPromptStatusListener(callback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "registerBiometricPromptOnKeyguardCallback(): Service not connected"); + } + } + /** * Requests all {@link Authenticators.Types#BIOMETRIC_STRONG} sensors to have their * authenticatorId invalidated for the specified user. This happens when enrollments have been diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl index c2e5c0b6d51925ec48e51f28e4634fbab26cf829..8eede472bec5982d181b09623ed77e50b155bfb5 100644 --- a/core/java/android/hardware/biometrics/IAuthService.aidl +++ b/core/java/android/hardware/biometrics/IAuthService.aidl @@ -17,6 +17,7 @@ package android.hardware.biometrics; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.biometrics.IBiometricPromptStatusListener; import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; @@ -63,6 +64,9 @@ interface IAuthService { // Register callback for when keyguard biometric eligibility changes. void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback); + // Register callback to check biometric prompt status. + void registerBiometricPromptStatusListener(IBiometricPromptStatusListener callback); + // Requests all BIOMETRIC_STRONG sensors to have their authenticatorId invalidated for the // specified user. This happens when enrollments have been added on devices with multiple // biometric sensors. diff --git a/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl b/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..7a0f4389ed6025f05e3750c6d9b2cb365e8884bd --- /dev/null +++ b/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl @@ -0,0 +1,27 @@ +/* + * 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 android.hardware.biometrics; + +/** + * Communication channel to propagate biometric prompt status. Implementation of this interface + * should be registered in BiometricService#registerBiometricPromptStatusListener. + * @hide + */ +oneway interface IBiometricPromptStatusListener { + void onBiometricPromptShowing(); + void onBiometricPromptIdle(); +} \ No newline at end of file diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index 18c8d1bd3a1e3ae23d6388f11c46f575d7126175..36606a135f3ebd27f0d1537e08a079f471aeb1e0 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -17,6 +17,7 @@ package android.hardware.biometrics; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.biometrics.IBiometricPromptStatusListener; import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IInvalidationCallback; @@ -68,6 +69,10 @@ interface IBiometricService { @EnforcePermission("USE_BIOMETRIC_INTERNAL") void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback); + // Register a callback for biometric prompt status on keyguard. + @EnforcePermission("USE_BIOMETRIC_INTERNAL") + void registerBiometricPromptStatusListener(IBiometricPromptStatusListener callback); + // Notify BiometricService when <Biometric>Service is ready to start the prepared client. // Client lifecycle is still managed in <Biometric>Service. @EnforcePermission("USE_BIOMETRIC_INTERNAL") diff --git a/core/java/android/os/PowerMonitor.java b/core/java/android/os/PowerMonitor.java index 5fb0df7febc1c0b18f68a170b5c07fce32dfb108..8c5f2cd48f61d62680dc198869696d5250049129 100644 --- a/core/java/android/os/PowerMonitor.java +++ b/core/java/android/os/PowerMonitor.java @@ -16,6 +16,7 @@ package android.os; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; @@ -23,12 +24,19 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** - * A PowerMonitor represents either a Channel aka ODPM rail (on-device power monitor) or an - * EnergyConsumer, as defined in - * <a href="https://android.googlesource.com/platform/hardware/interfaces/+/refs/heads/main/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats">android.hardware.power.stats</a> - * - * @hide + * A PowerMonitor represents either an ODPM rail (on-device power rail monitor) or a modeled + * energy consumer. + * <p/> + * ODPM rail names are device-specific. No assumptions should be made about the names and + * exact purpose of ODPM rails across different device models. A rail name may be something + * like "S2S_VDD_G3D"; specific knowledge of the device hardware is required to interpret + * the corresponding power monitor data. + * <p/> + * Energy consumer have more human-readable names, e.g. "GPU", "MODEM" etc. However, developers + * must be extra cautious about using energy consumers across different device models, + * as their exact implementations are also hardware dependent and are customized by OEMs. */ +@FlaggedApi("com.android.server.power.optimization.power_monitor_api") public final class PowerMonitor implements Parcelable { /** @@ -36,9 +44,9 @@ public final class PowerMonitor implements Parcelable { * power rail measurement, or modeled in some fashion. For example, an energy consumer may * represent a combination of multiple rails or a portion of a rail shared between subsystems, * e.g. WiFi and Bluetooth are often handled by the same chip, powered by a shared rail. - * Some consumer names are standardized (see android.hardware.power.stats.EnergyConsumerType), - * others are not. + * Some consumer names are standardized, others are not. */ + @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int POWER_MONITOR_TYPE_CONSUMER = 0; /** @@ -46,6 +54,7 @@ public final class PowerMonitor implements Parcelable { * no assumptions can be made about the source of those measurements across different devices, * even if they have the same name. */ + @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int POWER_MONITOR_TYPE_MEASUREMENT = 1; /** @hide */ @@ -64,38 +73,60 @@ public final class PowerMonitor implements Parcelable { * @hide */ public final int index; + @PowerMonitorType - public final int type; + private final int mType; @NonNull - public final String name; + private final String mName; /** * @hide */ public PowerMonitor(int index, int type, @NonNull String name) { this.index = index; - this.type = type; - this.name = name; + this.mType = type; + this.mName = name; + } + + /** + * Returns the type of the power monitor. + */ + @FlaggedApi("com.android.server.power.optimization.power_monitor_api") + @PowerMonitorType + public int getType() { + return mType; + } + + /** + * Returns the name of the power monitor, either a power rail or an energy consumer. + */ + @FlaggedApi("com.android.server.power.optimization.power_monitor_api") + @NonNull + public String getName() { + return mName; } private PowerMonitor(Parcel in) { index = in.readInt(); - type = in.readInt(); - name = in.readString(); + mType = in.readInt(); + mName = in.readString8(); } + @FlaggedApi("com.android.server.power.optimization.power_monitor_api") @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(index); - dest.writeInt(type); - dest.writeString(name); + dest.writeInt(mType); + dest.writeString8(mName); } + @FlaggedApi("com.android.server.power.optimization.power_monitor_api") @Override public int describeContents() { return 0; } + @FlaggedApi("com.android.server.power.optimization.power_monitor_api") @NonNull public static final Creator<PowerMonitor> CREATOR = new Creator<>() { @Override diff --git a/core/java/android/os/PowerMonitorReadings.java b/core/java/android/os/PowerMonitorReadings.java index e76705917c7a2b0983c7b916caa07c93a24933ca..bb677d529507e6362aad6c8baccc8a1104aaceea 100644 --- a/core/java/android/os/PowerMonitorReadings.java +++ b/core/java/android/os/PowerMonitorReadings.java @@ -17,6 +17,7 @@ package android.os; import android.annotation.ElapsedRealtimeLong; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import java.util.Arrays; @@ -24,10 +25,10 @@ import java.util.Comparator; /** * A collection of energy measurements from Power Monitors. - * - * @hide */ +@FlaggedApi("com.android.server.power.optimization.power_monitor_api") public final class PowerMonitorReadings { + @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int ENERGY_UNAVAILABLE = -1; @NonNull @@ -41,7 +42,7 @@ public final class PowerMonitorReadings { Comparator.comparingInt(pm -> pm.index); /** - * @param powerMonitors array of power monitor (ODPM) rails, sorted by PowerMonitor.index + * @param powerMonitors array of power monitor, sorted by PowerMonitor.index * @hide */ public PowerMonitorReadings(@NonNull PowerMonitor[] powerMonitors, @@ -56,7 +57,8 @@ public final class PowerMonitorReadings { * Does not persist across reboots. * Represents total energy: both on-battery and plugged-in. */ - public long getConsumedEnergyUws(@NonNull PowerMonitor powerMonitor) { + @FlaggedApi("com.android.server.power.optimization.power_monitor_api") + public long getConsumedEnergy(@NonNull PowerMonitor powerMonitor) { int offset = Arrays.binarySearch(mPowerMonitors, powerMonitor, POWER_MONITOR_COMPARATOR); if (offset >= 0) { return mEnergyUws[offset]; @@ -67,6 +69,7 @@ public final class PowerMonitorReadings { /** * Elapsed realtime, in milliseconds, when the snapshot was taken. */ + @FlaggedApi("com.android.server.power.optimization.power_monitor_api") @ElapsedRealtimeLong public long getTimestamp(@NonNull PowerMonitor powerMonitor) { int offset = Arrays.binarySearch(mPowerMonitors, powerMonitor, POWER_MONITOR_COMPARATOR); @@ -84,7 +87,7 @@ public final class PowerMonitorReadings { if (i != 0) { sb.append(", "); } - sb.append(mPowerMonitors[i].name) + sb.append(mPowerMonitors[i].getName()) .append(" = ").append(mEnergyUws[i]) .append(" (").append(mTimestampsMs[i]).append(')'); } diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java index dfc43f46c57dba8ff23ddf0ee145db92f04561e4..2d53341be654f431676c682937ae32bfb6cae465 100644 --- a/core/java/android/os/health/SystemHealthManager.java +++ b/core/java/android/os/health/SystemHealthManager.java @@ -16,6 +16,7 @@ package android.os.health; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemService; @@ -24,7 +25,6 @@ import android.content.Context; import android.os.BatteryStats; import android.os.Build; import android.os.Bundle; -import android.os.ConditionVariable; import android.os.Handler; import android.os.IPowerStatsService; import android.os.PowerMonitor; @@ -157,39 +157,15 @@ public class SystemHealthManager { } /** - * Returns a list of supported power monitors, which include raw ODPM rails and - * modeled energy consumers. If ODPM is unsupported by PowerStats HAL, this method returns - * an empty array. + * Asynchronously retrieves a list of supported {@link PowerMonitor}'s, which include raw ODPM + * (on-device power rail monitor) rails and modeled energy consumers. If ODPM is unsupported + * on this device this method delivers an empty list. * - * @hide - */ - @NonNull - public List<PowerMonitor> getSupportedPowerMonitors() { - synchronized (mPowerMonitorsLock) { - if (mPowerMonitorsInfo != null) { - return mPowerMonitorsInfo; - } - } - ConditionVariable lock = new ConditionVariable(); - // Populate mPowerMonitorsInfo by side-effect - getSupportedPowerMonitors(null, unused -> lock.open()); - lock.block(); - - synchronized (mPowerMonitorsLock) { - return mPowerMonitorsInfo; - } - } - - /** - * Asynchronously retrieves a list of supported power monitors, see - * {@link #getSupportedPowerMonitors()} - * - * @param handler optional Handler to deliver the callback. If not supplied, the callback - * may be invoked on an arbitrary thread. + * @param handler optional Handler to deliver the callback. If not supplied, the callback + * may be invoked on an arbitrary thread. * @param onResult callback for the result - * - * @hide */ + @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getSupportedPowerMonitors(@Nullable Handler handler, @NonNull Consumer<List<PowerMonitor>> onResult) { final List<PowerMonitor> result; @@ -229,35 +205,6 @@ public class SystemHealthManager { } } - /** - * Retrieves the accumulated power consumption reported by the specified power monitors. - * - * @param powerMonitors power monitors to be returned. - * - * @hide - */ - @NonNull - public PowerMonitorReadings getPowerMonitorReadings(@NonNull List<PowerMonitor> powerMonitors) { - PowerMonitorReadings[] outReadings = new PowerMonitorReadings[1]; - RuntimeException[] outException = new RuntimeException[1]; - ConditionVariable lock = new ConditionVariable(); - getPowerMonitorReadings(powerMonitors, null, - pms -> { - outReadings[0] = pms; - lock.open(); - }, - error -> { - outException[0] = error; - lock.open(); - } - ); - lock.block(); - if (outException[0] != null) { - throw outException[0]; - } - return outReadings[0]; - } - private static final Comparator<PowerMonitor> POWER_MONITOR_COMPARATOR = Comparator.comparingInt(pm -> pm.index); @@ -270,9 +217,8 @@ public class SystemHealthManager { * may be invoked on an arbitrary thread. * @param onSuccess callback for the result * @param onError callback invoked in case of an error - * - * @hide */ + @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getPowerMonitorReadings(@NonNull List<PowerMonitor> powerMonitors, @Nullable Handler handler, @NonNull Consumer<PowerMonitorReadings> onSuccess, @NonNull Consumer<RuntimeException> onError) { diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig index 88f62f327fdd59bdf4a0166634b7da4911f35d29..66ad12c7559ae3663e4e08f0745327ccbddf1c19 100644 --- a/core/java/android/os/vibrator/flags.aconfig +++ b/core/java/android/os/vibrator/flags.aconfig @@ -27,3 +27,13 @@ flag { description: "Enables the APIs for vibration serialization/deserialization." bug: "245129509" } + +flag { + namespace: "haptics" + name: "haptic_feedback_vibration_oem_customization_enabled" + description: "Enables OEMs/devices to customize vibrations for haptic feedback" + # Make read only. This is because the flag is used only once, and this could happen before + # the read-write flag values propagate to the device. + is_fixed_read_only: true + bug: "291128479" +} diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java index df934335e49d6fa29d0d9057969fb98ccd5c6c59..5d96f69c2929a3f98d136ada3f0df2ea3ced1a5e 100644 --- a/core/java/android/service/credentials/BeginCreateCredentialResponse.java +++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.java @@ -143,7 +143,7 @@ public final class BeginCreateCredentialResponse implements Parcelable { * * <p> Note that as a provider service you will only be able to set a remote entry if : * - Provider service possesses the - * {@link Manifest.permission.PROVIDE_REMOTE_CREDENTIALS} permission. + * {@link Manifest.permission#PROVIDE_REMOTE_CREDENTIALS} permission. * - Provider service is configured as the provider that can provide remote entries. * * If the above conditions are not met, setting back {@link BeginCreateCredentialResponse} diff --git a/core/java/android/service/credentials/BeginGetCredentialResponse.java b/core/java/android/service/credentials/BeginGetCredentialResponse.java index 5ed06ac1ade7037d3338d2abcb373f491135858c..ae6ca25a38ad7ccd091c7311ab8c6096d6f3bd7a 100644 --- a/core/java/android/service/credentials/BeginGetCredentialResponse.java +++ b/core/java/android/service/credentials/BeginGetCredentialResponse.java @@ -160,7 +160,7 @@ public final class BeginGetCredentialResponse implements Parcelable { * * <p> Note that as a provider service you will only be able to set a remote entry if : * - Provider service possesses the - * {@link Manifest.permission.PROVIDE_REMOTE_CREDENTIALS} permission. + * {@link Manifest.permission#PROVIDE_REMOTE_CREDENTIALS} permission. * - Provider service is configured as the provider that can provide remote entries. * * If the above conditions are not met, setting back {@link BeginGetCredentialResponse} diff --git a/core/java/android/service/credentials/CallingAppInfo.java b/core/java/android/service/credentials/CallingAppInfo.java index e755581638f50678b49bdc787bbd784a5ea0181a..c3524c5c85aa70b04ad201678edc8fcfc153e780 100644 --- a/core/java/android/service/credentials/CallingAppInfo.java +++ b/core/java/android/service/credentials/CallingAppInfo.java @@ -103,7 +103,7 @@ public final class CallingAppInfo implements Parcelable { * of other applications. * * Android system makes sure that only applications that poses the permission - * {@link android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN} can set the origin on + * {@link android.Manifest.permission#CREDENTIAL_MANAGER_SET_ORIGIN} can set the origin on * the incoming {@link android.credentials.GetCredentialRequest} or * {@link android.credentials.CreateCredentialRequest}. */ diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl index d554514349c31c6efae43dc59a8b52db525f885a..11180aef4479f9c210d80ef6e490768227aad10f 100644 --- a/core/java/android/view/IWindow.aidl +++ b/core/java/android/view/IWindow.aidl @@ -54,6 +54,10 @@ oneway interface IWindow { */ void executeCommand(String command, String parameters, in ParcelFileDescriptor descriptor); + /** + * Please dispatch through WindowStateResizeItem instead of directly calling this method from + * the system server. + */ void resized(in ClientWindowFrames frames, boolean reportDraw, in MergedConfiguration newMergedConfiguration, in InsetsState insetsState, boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index c35b6907cd9d6b7d02a952af3a2b3479962d4146..9f886c826174d570c551f00bcb434b15e34b16fa 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 78716f5646f69eace6d70d0da2934e6d3d6f4722..0ba4148172476ac2b0cd82487957609639c2335c 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 b080b711572881e590c82f6b32143e898d338ab5..2f3d73a3642566cb71c34d01375cd48861b59eba 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/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index e111dc8088de5436916d2bc5e53a250a782e0ebc..b5648cc90dcdcdfa1addb8d8e35c31846d325dac 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1730,7 +1730,7 @@ public final class ViewRootImpl implements ViewParent, attrs.getTitle().toString()); mAttachInfo.mThreadedRenderer = renderer; renderer.setSurfaceControl(mSurfaceControl, mBlastBufferQueue); - updateColorModeIfNeeded(attrs.getColorMode()); + updateColorModeIfNeeded(attrs.getColorMode(), attrs.getDesiredHdrHeadroom()); updateRenderHdrSdrRatio(); updateForceDarkMode(); mAttachInfo.mHardwareAccelerated = true; @@ -3349,7 +3349,7 @@ public final class ViewRootImpl implements ViewParent, } final boolean alwaysConsumeSystemBarsChanged = mPendingAlwaysConsumeSystemBars != mAttachInfo.mAlwaysConsumeSystemBars; - updateColorModeIfNeeded(lp.getColorMode()); + updateColorModeIfNeeded(lp.getColorMode(), lp.getDesiredHdrHeadroom()); surfaceCreated = !hadSurface && mSurface.isValid(); surfaceDestroyed = hadSurface && !mSurface.isValid(); @@ -5652,7 +5652,8 @@ public final class ViewRootImpl implements ViewParent, mUpdateHdrSdrRatioInfo = true; } - private void updateColorModeIfNeeded(@ActivityInfo.ColorMode int colorMode) { + private void updateColorModeIfNeeded(@ActivityInfo.ColorMode int colorMode, + float desiredRatio) { if (mAttachInfo.mThreadedRenderer == null) { return; } @@ -5666,7 +5667,10 @@ public final class ViewRootImpl implements ViewParent, && !getConfiguration().isScreenWideColorGamut()) { colorMode = ActivityInfo.COLOR_MODE_DEFAULT; } - float desiredRatio = mAttachInfo.mThreadedRenderer.setColorMode(colorMode); + float automaticRatio = mAttachInfo.mThreadedRenderer.setColorMode(colorMode); + if (desiredRatio == 0 || desiredRatio > automaticRatio) { + desiredRatio = automaticRatio; + } if (desiredRatio != mDesiredHdrSdrRatio) { mDesiredHdrSdrRatio = desiredRatio; updateRenderHdrSdrRatio(); @@ -10543,6 +10547,8 @@ public final class ViewRootImpl implements ViewParent, MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing) { + // Although this is a AIDL method, it will only be triggered in local process through + // either WindowStateResizeItem or WindowlessWindowManager. final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, insetsState, diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 2f04b0c695daca7805304b01570e56e461418002..87537fbc996124a68a301d55f309c6ca98d3c0c1 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -24,6 +24,8 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE import android.annotation.ColorInt; import android.annotation.DrawableRes; +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; import android.annotation.IdRes; import android.annotation.LayoutRes; import android.annotation.NonNull; @@ -1329,6 +1331,47 @@ public abstract class Window { dispatchWindowAttributesChanged(attrs); } + /** + * <p>Sets the desired about of HDR headroom to be used when rendering as a ratio of + * targetHdrPeakBrightnessInNits / targetSdrWhitePointInNits. Only applies when + * {@link #setColorMode(int)} is {@link ActivityInfo#COLOR_MODE_HDR}</p> + * + * <p>By default the system will choose an amount of HDR headroom that is appropriate + * for the underlying device capabilities & bit-depth of the panel. However, for some types + * of content this can end up being more headroom than necessary or desired. An example + * would be a messaging app or gallery thumbnail view where some amount of HDR pop is desired + * without overly influencing the perceived brightness of the majority SDR content. This can + * also be used to animate in/out of an HDR range for smoother transitions.</p> + * + * <p>Note: The actual amount of HDR headroom that will be given is subject to a variety + * of factors such as ambient conditions, display capabilities, or bit-depth limitations. + * See {@link Display#getHdrSdrRatio()} for more information as well as how to query the + * current value.</p> + * + * @param desiredHeadroom The amount of HDR headroom that is desired. Must be >= 1.0 (no HDR) + * and <= 10,000.0. Passing 0.0 will reset to the default, automatically + * chosen value. + * @see #getDesiredHdrHeadroom() + * @see Display#getHdrSdrRatio() + */ + @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR) + public void setDesiredHdrHeadroom( + @FloatRange(from = 0.0f, to = 10000.0) float desiredHeadroom) { + final WindowManager.LayoutParams attrs = getAttributes(); + attrs.setDesiredHdrHeadroom(desiredHeadroom); + dispatchWindowAttributesChanged(attrs); + } + + /** + * Get the desired amount of HDR headroom as set by {@link #setDesiredHdrHeadroom(float)} + * @return The amount of HDR headroom set, or 0 for automatic/default behavior. + * @see #setDesiredHdrHeadroom(float) + */ + @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR) + public float getDesiredHdrHeadroom() { + return getAttributes().getDesiredHdrHeadroom(); + } + /** * If {@code isPreferred} is true, this method requests that the connected display does minimal * post processing when this window is visible on the screen. Otherwise, it requests that the diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 08f9c8cdd2a86a57b29b826e1ca6bca4c4748eaf..a3b93b433ddad277f777b49a5d94db87857671a2 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -83,6 +83,7 @@ import static android.view.WindowLayoutParamsProto.Y; import android.Manifest.permission; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; +import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -1684,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. @@ -1692,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) { @@ -3114,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 @@ -4315,6 +4316,9 @@ public interface WindowManager extends ViewManager { @ActivityInfo.ColorMode private int mColorMode = COLOR_MODE_DEFAULT; + /** @hide */ + private float mDesiredHdrHeadroom = 0; + /** * Carries the requests about {@link WindowInsetsController.Appearance} and * {@link WindowInsetsController.Behavior} to the system windows which can produce insets. @@ -4527,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) { @@ -4542,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; @@ -4716,6 +4720,39 @@ public interface WindowManager extends ViewManager { return mColorMode; } + /** + * <p>Sets the desired about of HDR headroom to be used when rendering as a ratio of + * targetHdrPeakBrightnessInNits / targetSdrWhitePointInNits. Only applies when + * {@link #setColorMode(int)} is {@link ActivityInfo#COLOR_MODE_HDR}</p> + * + * @see Window#setDesiredHdrHeadroom(float) + * @param desiredHeadroom Desired amount of HDR headroom. Must be in the range of 1.0 (SDR) + * to 10,000.0, or 0.0 to reset to default. + */ + @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR) + public void setDesiredHdrHeadroom( + @FloatRange(from = 0.0f, to = 10000.0f) float desiredHeadroom) { + if (!Float.isFinite(desiredHeadroom)) { + throw new IllegalArgumentException("desiredHeadroom must be finite: " + + desiredHeadroom); + } + if (desiredHeadroom != 0 && (desiredHeadroom < 1.0f || desiredHeadroom > 10000.0f)) { + throw new IllegalArgumentException( + "desiredHeadroom must be 0.0 or in the range [1.0, 10000.0f], received: " + + desiredHeadroom); + } + mDesiredHdrHeadroom = desiredHeadroom; + } + + /** + * Get the desired amount of HDR headroom as set by {@link #setDesiredHdrHeadroom(float)} + * @return The amount of HDR headroom set, or 0 for automatic/default behavior. + */ + @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR) + public float getDesiredHdrHeadroom() { + return mDesiredHdrHeadroom; + } + /** * <p> * Blurs the screen behind the window. The effect is similar to that of {@link #dimAmount}, @@ -4866,6 +4903,7 @@ public interface WindowManager extends ViewManager { checkNonRecursiveParams(); out.writeTypedArray(paramsForRotation, 0 /* parcelableFlags */); out.writeInt(mDisplayFlags); + out.writeFloat(mDesiredHdrHeadroom); } public static final @android.annotation.NonNull Parcelable.Creator<LayoutParams> CREATOR @@ -4937,6 +4975,7 @@ public interface WindowManager extends ViewManager { forciblyShownTypes = in.readInt(); paramsForRotation = in.createTypedArray(LayoutParams.CREATOR); mDisplayFlags = in.readInt(); + mDesiredHdrHeadroom = in.readFloat(); } @SuppressWarnings({"PointlessBitwiseExpression"}) @@ -5197,6 +5236,11 @@ public interface WindowManager extends ViewManager { changes |= COLOR_MODE_CHANGED; } + if (mDesiredHdrHeadroom != o.mDesiredHdrHeadroom) { + mDesiredHdrHeadroom = o.mDesiredHdrHeadroom; + changes |= COLOR_MODE_CHANGED; + } + if (preferMinimalPostProcessing != o.preferMinimalPostProcessing) { preferMinimalPostProcessing = o.preferMinimalPostProcessing; changes |= MINIMAL_POST_PROCESSING_PREFERENCE_CHANGED; @@ -5424,6 +5468,9 @@ public interface WindowManager extends ViewManager { if (mColorMode != COLOR_MODE_DEFAULT) { sb.append(" colorMode=").append(ActivityInfo.colorModeToString(mColorMode)); } + if (mDesiredHdrHeadroom != 0) { + sb.append(" desiredHdrHeadroom=").append(mDesiredHdrHeadroom); + } if (preferMinimalPostProcessing) { sb.append(" preferMinimalPostProcessing="); sb.append(preferMinimalPostProcessing); diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java index 7ad43c76efaa9d415774bea0ddfef904e64525f1..26298bc645ad0cf1b5aa7b0e36a8d3f6a0f89d3e 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 e6a8b7827b04a99096844ddae3abbaaa42542305..43bfe139c223430f1fb35f9bac84a29cec632746 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 a76780d18f7120ffd6247ef1d194945a12b666de..32256b9b09c8e55714b6236d5fef4dd587c88828 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 dc3d32317ded3362040eb0cc0486bd3441e9c559..bb815c0e831761ba20b32367cc04c2b6ee119a3b 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/contentprotection/flags/content_protection_flags.aconfig b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig index 7e06f87d931a69df6b110541fb20cd9c76380dbc..5c86feb3c22c463573822b61a69f6129a3f73f32 100644 --- a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig +++ b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig @@ -6,3 +6,10 @@ flag { description: "If true, content protection blocklist is mutable and can be updated." bug: "301658008" } + +flag { + name: "parse_groups_config_enabled" + namespace: "content_protection" + description: "If true, content protection groups config will be parsed." + bug: "302187922" +} diff --git a/core/java/android/view/displayhash/DisplayHashResultCallback.java b/core/java/android/view/displayhash/DisplayHashResultCallback.java index 6e3d9a8786af0b59bf3336b524a973f9cc127a84..927874ffc3a2e1303202f99230eb48711c5e17f7 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 a92420a2f373473f69bc2ff5e7ef8d40d86bcf22..c5114b9550dbf2fa591d72965051326dc75b2112 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 5bb1e93181755a55059bff1698f1c434cef7f2f2..8159af3ddd4a7461483bf89574e7672684ab7c61 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 5be0e3f3ccf2e582f580799cd0cd0887a2e230c5..78de7e16edd7dbc94c84b62899be487625d97b56 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 07ac2922304ecce8d87b4b34698bdece4b821574..2736b68845a2b4e144e2ef829eec93782c6b0c27 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 d2a16a3a9212638d2a9342c85f3176244d321dfa..61f340a856c41962aee419ac601521633e2bfe08 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; @@ -360,6 +361,15 @@ public final class TransitionInfo implements Parcelable { mChanges.add(change); } + /** + * 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. */ diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index ec5d4ff16ee5d63599865a2d45173046fa27ada0..24dc6dbfede846d968c543f2061fe57c9ba0502a 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -22,3 +22,10 @@ flag { description: "Whether the TaskFragment system organizer feature is enabled" bug: "284050041" } + +flag { + namespace: "windowing_sdk" + name: "window_state_resize_item_flag" + description: "Whether to dispatch window resize through ClientTransaction is enabled" + bug: "301870955" +} diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java index 58376a77c7052b806a37d864e01c71c51b76e768..0801dd8c0bd8015fe443d5d2f55070a7554cff68 100644 --- a/core/java/com/android/internal/content/FileSystemProvider.java +++ b/core/java/com/android/internal/content/FileSystemProvider.java @@ -62,16 +62,14 @@ import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; + import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; -import java.util.Deque; -import java.util.LinkedList; import java.util.List; import java.util.Locale; +import java.util.Queue; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.function.Predicate; -import java.util.regex.Pattern; /** * A helper class for {@link android.provider.DocumentsProvider} to perform file operations on local @@ -89,6 +87,8 @@ public abstract class FileSystemProvider extends DocumentsProvider { DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER, DocumentsContract.QUERY_ARG_MIME_TYPES); + private static final int MAX_RESULTS_NUMBER = 23; + private static String joinNewline(String... args) { return TextUtils.join("\n", args); } @@ -375,62 +375,53 @@ public abstract class FileSystemProvider extends DocumentsProvider { } /** - * This method is similar to - * {@link DocumentsProvider#queryChildDocuments(String, String[], String)}. This method returns - * all children documents including hidden directories/files. - * - * <p> - * In a scoped storage world, access to "Android/data" style directories are hidden for privacy - * reasons. This method may show privacy sensitive data, so its usage should only be in - * restricted modes. - * - * @param parentDocumentId the directory to return children for. - * @param projection list of {@link Document} columns to put into the - * cursor. If {@code null} all supported columns should be - * included. - * @param sortOrder how to order the rows, formatted as an SQL - * {@code ORDER BY} clause (excluding the ORDER BY itself). - * Passing {@code null} will use the default sort order, which - * may be unordered. This ordering is a hint that can be used to - * prioritize how data is fetched from the network, but UI may - * always enforce a specific ordering - * @throws FileNotFoundException when parent document doesn't exist or query fails + * WARNING: this method should really be {@code final}, but for the backward compatibility it's + * not; new classes that extend {@link FileSystemProvider} should override + * {@link #queryChildDocuments(String, String[], String, boolean)}, not this method. */ - protected Cursor queryChildDocumentsShowAll( - String parentDocumentId, String[] projection, String sortOrder) + @Override + public Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder) throws FileNotFoundException { - return queryChildDocuments(parentDocumentId, projection, sortOrder, File -> true); + return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ false); } + /** + * This method is similar to {@link #queryChildDocuments(String, String[], String)}, however, it + * could return <b>all</b> content of the directory, <b>including restricted (hidden) + * directories and files</b>. + * <p> + * In the scoped storage world, some directories and files (e.g. {@code Android/data/} and + * {@code Android/obb/} on the external storage) are hidden for privacy reasons. + * Hence, this method may reveal privacy-sensitive data, thus should be used with extra care. + */ @Override - public Cursor queryChildDocuments( - String parentDocumentId, String[] projection, String sortOrder) - throws FileNotFoundException { - // Access to some directories is hidden for privacy reasons. - return queryChildDocuments(parentDocumentId, projection, sortOrder, this::shouldShow); + public final Cursor queryChildDocumentsForManage(String documentId, String[] projection, + String sortOrder) throws FileNotFoundException { + return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ true); } - private Cursor queryChildDocuments( - String parentDocumentId, String[] projection, String sortOrder, - @NonNull Predicate<File> filter) throws FileNotFoundException { - final File parent = getFileForDocId(parentDocumentId); + protected Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder, + boolean includeHidden) throws FileNotFoundException { + final File parent = getFileForDocId(documentId); final MatrixCursor result = new DirectoryCursor( - resolveProjection(projection), parentDocumentId, parent); + resolveProjection(projection), documentId, parent); + + if (!parent.isDirectory()) { + Log.w(TAG, '"' + documentId + "\" is not a directory"); + return result; + } - if (!filter.test(parent)) { - Log.w(TAG, "No permission to access parentDocumentId: " + parentDocumentId); + if (!includeHidden && shouldHideDocument(documentId)) { + Log.w(TAG, "Queried directory \"" + documentId + "\" is hidden"); return result; } - if (parent.isDirectory()) { - for (File file : FileUtils.listFilesOrEmpty(parent)) { - if (filter.test(file)) { - includeFile(result, null, file); - } - } - } else { - Log.w(TAG, "parentDocumentId '" + parentDocumentId + "' is not Directory"); + for (File file : FileUtils.listFilesOrEmpty(parent)) { + if (!includeHidden && shouldHideDocument(file)) continue; + + includeFile(result, null, file); } + return result; } @@ -452,23 +443,29 @@ public abstract class FileSystemProvider extends DocumentsProvider { * * @see ContentResolver#EXTRA_HONORED_ARGS */ - protected final Cursor querySearchDocuments( - File folder, String[] projection, Set<String> exclusion, Bundle queryArgs) - throws FileNotFoundException { + protected final Cursor querySearchDocuments(File folder, String[] projection, + Set<String> exclusion, Bundle queryArgs) throws FileNotFoundException { final MatrixCursor result = new MatrixCursor(resolveProjection(projection)); - final List<File> pending = new ArrayList<>(); - pending.add(folder); - while (!pending.isEmpty() && result.getCount() < 24) { - final File file = pending.remove(0); - if (shouldHide(file)) continue; + + // We'll be a running a BFS here. + final Queue<File> pending = new ArrayDeque<>(); + pending.offer(folder); + + while (!pending.isEmpty() && result.getCount() < MAX_RESULTS_NUMBER) { + final File file = pending.poll(); + + // Skip hidden documents (both files and directories) + if (shouldHideDocument(file)) continue; if (file.isDirectory()) { for (File child : FileUtils.listFilesOrEmpty(file)) { - pending.add(child); + pending.offer(child); } } - if (!exclusion.contains(file.getAbsolutePath()) && matchSearchQueryArguments(file, - queryArgs)) { + + if (exclusion.contains(file.getAbsolutePath())) continue; + + if (matchSearchQueryArguments(file, queryArgs)) { includeFile(result, null, file); } } @@ -612,26 +609,23 @@ public abstract class FileSystemProvider extends DocumentsProvider { final int flagIndex = ArrayUtils.indexOf(columns, Document.COLUMN_FLAGS); if (flagIndex != -1) { + final boolean isDir = mimeType.equals(Document.MIME_TYPE_DIR); int flags = 0; if (file.canWrite()) { - if (mimeType.equals(Document.MIME_TYPE_DIR)) { + flags |= Document.FLAG_SUPPORTS_DELETE; + flags |= Document.FLAG_SUPPORTS_RENAME; + flags |= Document.FLAG_SUPPORTS_MOVE; + if (isDir) { flags |= Document.FLAG_DIR_SUPPORTS_CREATE; - flags |= Document.FLAG_SUPPORTS_DELETE; - flags |= Document.FLAG_SUPPORTS_RENAME; - flags |= Document.FLAG_SUPPORTS_MOVE; - - if (shouldBlockFromTree(docId)) { - flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE; - } - } else { flags |= Document.FLAG_SUPPORTS_WRITE; - flags |= Document.FLAG_SUPPORTS_DELETE; - flags |= Document.FLAG_SUPPORTS_RENAME; - flags |= Document.FLAG_SUPPORTS_MOVE; } } + if (isDir && shouldBlockDirectoryFromTree(docId)) { + flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE; + } + if (mimeType.startsWith("image/")) { flags |= Document.FLAG_SUPPORTS_THUMBNAIL; } @@ -664,22 +658,36 @@ public abstract class FileSystemProvider extends DocumentsProvider { return row; } - private static final Pattern PATTERN_HIDDEN_PATH = Pattern.compile( - "(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(?:data|obb|sandbox)$"); - /** - * In a scoped storage world, access to "Android/data" style directories are - * hidden for privacy reasons. + * Some providers may want to restrict access to certain directories and files, + * e.g. <i>"Android/data"</i> and <i>"Android/obb"</i> on the shared storage for + * privacy reasons. + * Such providers should override this method. */ - protected boolean shouldHide(@NonNull File file) { - return (PATTERN_HIDDEN_PATH.matcher(file.getAbsolutePath()).matches()); + protected boolean shouldHideDocument(@NonNull String documentId) + throws FileNotFoundException { + return false; } - private boolean shouldShow(@NonNull File file) { - return !shouldHide(file); + /** + * A variant of the {@link #shouldHideDocument(String)} that takes a {@link File} instead of + * a {@link String} {@code documentId}. + * + * @see #shouldHideDocument(String) + */ + protected final boolean shouldHideDocument(@NonNull File document) + throws FileNotFoundException { + return shouldHideDocument(getDocIdForFile(document)); } - protected boolean shouldBlockFromTree(@NonNull String docId) { + /** + * @return if the directory that should be blocked from being selected when the user launches + * an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} intent. + * + * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE + */ + protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId) + throws FileNotFoundException { return false; } diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index b5d70d379e0cebee42d3f1e412aaa79411043cf6..50253cf9e457515586c9c42c4cc93b4722ef80fb 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -1315,7 +1315,16 @@ void AndroidRuntime::exit(int code) ALOGI("VM exiting with result code %d.", code); onExit(code); } + +#ifdef __ANDROID_CLANG_COVERAGE__ + // When compiled with coverage, a function is registered with atexit to call + // `__llvm_profile_write_file` when the process exit. + // For Clang code coverage to work, call exit instead of _exit to run hooks + // registered with atexit. + ::exit(code); +#else ::_exit(code); +#endif } void AndroidRuntime::onVmCreated(JNIEnv* env) diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index ba644eb0c03ec3eccc547ba538f25ffd4860fabe..178c0d0d95be807f2096f13bb1e8d71a6d1f7c8c 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 ca768ad434f1583a2aec238f0ecdf9f54ff7cac1..a54a563b1804a9a76c2bd48e3de27275f0a824fd 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/res/res/values/strings.xml b/core/res/res/values/strings.xml index a2a4e34f352796fe468c1e7835445369cdee5691..41356bd495148ae5109638d3535151eef93237ff 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -6298,11 +6298,6 @@ ul.</string> <!-- Content of connected display unavailable notification. [CHAR LIMIT=NONE] --> <string name="connected_display_unavailable_notification_content">Use a different cable and try again</string> - <!-- Title of cable don't support displays notifications. [CHAR LIMIT=NONE] --> - <string name="connected_display_cable_dont_support_displays_notification_title">Cable may not support displays</string> - <!-- Content of cable don't support displays notification. [CHAR LIMIT=NONE] --> - <string name="connected_display_cable_dont_support_displays_notification_content">Your USB-C cable may not connect to displays properly</string> - <!-- Name of concurrent display notifications. [CHAR LIMIT=NONE] --> <string name="concurrent_display_notification_name">Dual screen</string> <!-- Title of concurrent display active notification. [CHAR LIMIT=NONE] --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c1ecb05a6df2a1dc22e87bcc2a76418bffdc57cb..efe5c3bcf2dfdb56d36e88d480c4c27aa7d317c8 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5070,8 +5070,6 @@ <java-symbol type="array" name="device_state_notification_power_save_contents"/> <java-symbol type="string" name="connected_display_unavailable_notification_title"/> <java-symbol type="string" name="connected_display_unavailable_notification_content"/> - <java-symbol type="string" name="connected_display_cable_dont_support_displays_notification_title"/> - <java-symbol type="string" name="connected_display_cable_dont_support_displays_notification_content"/> <java-symbol type="string" name="concurrent_display_notification_name"/> <java-symbol type="string" name="concurrent_display_notification_active_title"/> <java-symbol type="string" name="concurrent_display_notification_active_content"/> diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c00eb91d752a65bd3911ff094c2d4228f5db5fc6 --- /dev/null +++ b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java @@ -0,0 +1,80 @@ +/* + * 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 android.app.servertransaction; + +import static org.mockito.Mockito.verify; + +import android.app.ClientTransactionHandler; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.util.MergedConfiguration; +import android.view.IWindow; +import android.view.InsetsState; +import android.window.ClientWindowFrames; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link WindowStateResizeItem}. + * + * Build/Install/Run: + * atest FrameworksCoreTests:WindowStateResizeItemTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class WindowStateResizeItemTest { + + @Mock + private ClientTransactionHandler mHandler; + @Mock + private PendingTransactionActions mPendingActions; + @Mock + private IWindow mWindow; + @Mock + private ClientWindowFrames mFrames; + @Mock + private MergedConfiguration mConfiguration; + @Mock + private InsetsState mInsetsState; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testExecute() throws RemoteException { + final WindowStateResizeItem item = WindowStateResizeItem.obtain(mWindow, mFrames, + true /* reportDraw */, mConfiguration, mInsetsState, true /* forceLayout */, + true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */, + true /* dragResizing */); + item.execute(mHandler, mPendingActions); + + verify(mWindow).resized(mFrames, + true /* reportDraw */, mConfiguration, mInsetsState, true /* forceLayout */, + true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */, + true /* dragResizing */); + } +} diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java index 0941a2b6d263bf80cd405db56f4ddcb3f2eb3961..6c14ee382bdce932fe5c095742bfaaffe8bee450 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/os/health/SystemHealthManagerTest.java b/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java deleted file mode 100644 index e1f9523f1b49489191e4f387b7b1e77764e40f26..0000000000000000000000000000000000000000 --- a/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.os.health; - -import static androidx.test.InstrumentationRegistry.getContext; - -import static com.google.common.truth.Truth.assertThat; - -import android.os.ConditionVariable; -import android.os.PowerMonitor; -import android.os.PowerMonitorReadings; - -import org.junit.Test; - -import java.util.ArrayList; -import java.util.List; - -public class SystemHealthManagerTest { - private List<PowerMonitor> mPowerMonitorInfo; - private PowerMonitorReadings mReadings; - private RuntimeException mException; - - @Test - public void getPowerMonitors() { - SystemHealthManager shm = getContext().getSystemService(SystemHealthManager.class); - List<PowerMonitor> powerMonitorInfo = shm.getSupportedPowerMonitors(); - assertThat(powerMonitorInfo).isNotNull(); - if (powerMonitorInfo.isEmpty()) { - // This device does not support PowerStats HAL - return; - } - - PowerMonitor consumerMonitor = null; - PowerMonitor measurementMonitor = null; - for (PowerMonitor pmi : powerMonitorInfo) { - if (pmi.type == PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) { - measurementMonitor = pmi; - } else { - consumerMonitor = pmi; - } - } - - List<PowerMonitor> selectedMonitors = new ArrayList<>(); - if (consumerMonitor != null) { - selectedMonitors.add(consumerMonitor); - } - if (measurementMonitor != null) { - selectedMonitors.add(measurementMonitor); - } - - PowerMonitorReadings readings = shm.getPowerMonitorReadings(selectedMonitors); - - for (PowerMonitor monitor : selectedMonitors) { - assertThat(readings.getConsumedEnergyUws(monitor)).isAtLeast(0); - assertThat(readings.getTimestamp(monitor)).isGreaterThan(0); - } - } - - @Test - public void getPowerMonitorsAsync() { - SystemHealthManager shm = getContext().getSystemService(SystemHealthManager.class); - ConditionVariable done = new ConditionVariable(); - shm.getSupportedPowerMonitors(null, pms -> { - mPowerMonitorInfo = pms; - done.open(); - }); - done.block(); - assertThat(mPowerMonitorInfo).isNotNull(); - if (mPowerMonitorInfo.isEmpty()) { - // This device does not support PowerStats HAL - return; - } - - PowerMonitor consumerMonitor = null; - PowerMonitor measurementMonitor = null; - for (PowerMonitor pmi : mPowerMonitorInfo) { - if (pmi.type == PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) { - measurementMonitor = pmi; - } else { - consumerMonitor = pmi; - } - } - - List<PowerMonitor> selectedMonitors = new ArrayList<>(); - if (consumerMonitor != null) { - selectedMonitors.add(consumerMonitor); - } - if (measurementMonitor != null) { - selectedMonitors.add(measurementMonitor); - } - - done.close(); - shm.getPowerMonitorReadings(selectedMonitors, null, - readings -> { - mReadings = readings; - done.open(); - }, - exception -> { - mException = exception; - done.open(); - } - ); - done.block(); - - assertThat(mException).isNull(); - - for (PowerMonitor monitor : selectedMonitors) { - assertThat(mReadings.getConsumedEnergyUws(monitor)).isAtLeast(0); - assertThat(mReadings.getTimestamp(monitor)).isGreaterThan(0); - } - } -} diff --git a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java index 263e563bc224f15f29ad3e237afcff27aaeb19aa..6229530dc33f07014bff6569cabaf2f5d9f184f6 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 63d1f6d6f2d6ebe188007ba8879f5f9db7d1e6f6..db37a38756d2a075155d200668f5fa7e46220956 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 0000000000000000000000000000000000000000..e030dad6bf14680baac1180c12ae134b38f75320 --- /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 b5fb13db4ae400331934a112204fae785cc7eecb..0a6fb84240948431a5ce9b7cc4bba19c01ffd4b1 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 c9c1b23d874cd5a8fd0a941af835f3ab0c40d3e3..deb89faf3419b811ff2787cf515bd3bcd0dd7f41 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 4461f39fd006220bd271840d9f7c8c347b517acd..c2a7a84ad8098f7253b58e1817a33a3b92d60b6d 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/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java index 8a6403705c1cc5b466b91798e5e70c2f4eefdcf5..c508d55507b8babeb2e7480835fbab9160fb17a1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java @@ -17,13 +17,19 @@ package com.android.wm.shell.dagger.pip; import android.annotation.NonNull; +import android.content.Context; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.dagger.WMShellBaseModule; import com.android.wm.shell.dagger.WMSingleton; +import com.android.wm.shell.pip2.phone.PipController; import com.android.wm.shell.pip2.phone.PipTransition; +import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; @@ -42,8 +48,21 @@ public abstract class Pip2Module { @NonNull ShellTaskOrganizer shellTaskOrganizer, @NonNull Transitions transitions, PipBoundsState pipBoundsState, - PipBoundsAlgorithm pipBoundsAlgorithm) { + PipBoundsAlgorithm pipBoundsAlgorithm, + PipController pipController) { return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null, pipBoundsAlgorithm); } + + @WMSingleton + @Provides + static PipController providePipController(Context context, + ShellInit shellInit, + ShellController shellController, + DisplayController displayController, + DisplayInsetsController displayInsetsController, + PipDisplayLayoutState pipDisplayLayoutState) { + return PipController.create(context, shellInit, shellController, displayController, + displayInsetsController, pipDisplayLayoutState); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java new file mode 100644 index 0000000000000000000000000000000000000000..186cb615f4ecf972aeb795055f637ce61c6f0961 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -0,0 +1,133 @@ +/* + * 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.wm.shell.pip2.phone; + +import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; + +import android.content.Context; +import android.content.res.Configuration; +import android.view.InsetsState; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipUtils; +import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.sysui.ConfigurationChangeListener; +import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; + +/** + * Manages the picture-in-picture (PIP) UI and states for Phones. + */ +public class PipController implements ConfigurationChangeListener, + DisplayController.OnDisplaysChangedListener { + private static final String TAG = PipController.class.getSimpleName(); + + private Context mContext; + private ShellController mShellController; + private DisplayController mDisplayController; + private DisplayInsetsController mDisplayInsetsController; + private PipDisplayLayoutState mPipDisplayLayoutState; + + private PipController(Context context, + ShellInit shellInit, + ShellController shellController, + DisplayController displayController, + DisplayInsetsController displayInsetsController, + PipDisplayLayoutState pipDisplayLayoutState) { + mContext = context; + mShellController = shellController; + mDisplayController = displayController; + mDisplayInsetsController = displayInsetsController; + mPipDisplayLayoutState = pipDisplayLayoutState; + + if (PipUtils.isPip2ExperimentEnabled()) { + shellInit.addInitCallback(this::onInit, this); + } + } + + private void onInit() { + // Ensure that we have the display info in case we get calls to update the bounds before the + // listener calls back + mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId()); + DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay()); + mPipDisplayLayoutState.setDisplayLayout(layout); + + mShellController.addConfigurationChangeListener(this); + mDisplayController.addDisplayWindowListener(this); + mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(), + new DisplayInsetsController.OnInsetsChangedListener() { + @Override + public void insetsChanged(InsetsState insetsState) { + onDisplayChanged(mDisplayController + .getDisplayLayout(mPipDisplayLayoutState.getDisplayId())); + } + }); + } + + /** + * Instantiates {@link PipController}, returns {@code null} if the feature not supported. + */ + public static PipController create(Context context, + ShellInit shellInit, + ShellController shellController, + DisplayController displayController, + DisplayInsetsController displayInsetsController, + PipDisplayLayoutState pipDisplayLayoutState) { + if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Device doesn't support Pip feature", TAG); + return null; + } + return new PipController(context, shellInit, shellController, displayController, + displayInsetsController, pipDisplayLayoutState); + } + + + @Override + public void onConfigurationChanged(Configuration newConfiguration) { + mPipDisplayLayoutState.onConfigurationChanged(); + } + + @Override + public void onThemeChanged() { + onDisplayChanged(new DisplayLayout(mContext, mContext.getDisplay())); + } + + @Override + public void onDisplayAdded(int displayId) { + if (displayId != mPipDisplayLayoutState.getDisplayId()) { + return; + } + onDisplayChanged(mDisplayController.getDisplayLayout(displayId)); + } + + @Override + public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { + if (displayId != mPipDisplayLayoutState.getDisplayId()) { + return; + } + onDisplayChanged(mDisplayController.getDisplayLayout(displayId)); + } + + private void onDisplayChanged(DisplayLayout layout) { + mPipDisplayLayoutState.setDisplayLayout(layout); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 226fe08a2f193cd061947c4cf4950392bf787a59..451e618559438a67df707ca1f747db2084222c6a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -345,6 +345,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, // Keyguard handler cannot handle it, process through original mixed mActiveTransitions.remove(keyguardMixed); } + } else if (mPipHandler != null) { + mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction); } } 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 87c438a5b37d8d8e840edf671e917b85dd22ac82..ba0ef20c412e4afade97a87a95b20a19e8cf7d1e 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/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml index b13e9a24857553eacc01e4618bcf23dfd263d3a6..1df11369a0490f7d7c3a0e8c93af3d3f7ba16238 100644 --- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml @@ -81,9 +81,7 @@ <option name="shell-timeout" value="6600s"/> <option name="test-timeout" value="6000s"/> <option name="hidden-api-checks" value="false"/> - <!-- TODO(b/288396763): re-enable when PerfettoListener is fixed <option name="device-listeners" value="android.device.collectors.PerfettoListener"/> - --> <!-- PerfettoListener related arguments --> <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/> <option name="instrumentation-arg" 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 b9c9049e08c49cee99afa911a12673e6fffd245e..da83d4c0a1222dc35799254729ba44d71309c737 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); @@ -1187,6 +1187,65 @@ public class ShellTransitionTests extends ShellTestCase { assertEquals(1, mDefaultHandler.activeCount()); } + @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(); diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig index d0d3c5e7ece115ad7dfc868f0388b58f7e6cbf54..e672b983d509cc071ab9512195a3c14bb448bf32 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 90850d36b61200c6eb3849a984c786317262dc12..22c58624870585ea64e5e6fc611e68e356a734fa 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 ceb3858eb0b3efed560a9f9751d10f8419212e69..b002bbf20c088559b463c34905f61d2299790a78 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/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index d9ed6a8fa1588dd3d8de0c386d9db12fc250012a..5b880797b7fd999f6919d1bf4dea85137c1bab09 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -1473,8 +1473,7 @@ public class AudioManager { * Returns the volume group id associated to the given {@link AudioAttributes}. * * @param attributes The {@link AudioAttributes} to consider. - * @return {@link android.media.audiopolicy.AudioVolumeGroup} id supporting the given - * {@link AudioAttributes} if found, + * @return audio volume group id supporting the given {@link AudioAttributes} if found, * {@code android.media.audiopolicy.AudioVolumeGroup.DEFAULT_VOLUME_GROUP} otherwise. */ public int getVolumeGroupIdForAttributes(@NonNull AudioAttributes attributes) { @@ -1589,7 +1588,7 @@ public class AudioManager { * <p> Call first in prior {@link #getVolumeGroupIdForAttributes(AudioAttributes)} to retrieve * the volume group id supporting the given {@link AudioAttributes}. * - * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider. + * @param groupId of the audio volume group to consider. * @param direction The direction to adjust the volume. One of * {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or * {@link #ADJUST_SAME}. @@ -1633,8 +1632,8 @@ public class AudioManager { * <p> Call first in prior {@link #getVolumeGroupIdForAttributes(AudioAttributes)} to retrieve * the volume group id supporting the given {@link AudioAttributes}. * - * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider. - * @return The mute state for the given {@link android.media.audiopolicy.AudioVolumeGroup} id. + * @param groupId of the audio volume group to consider. + * @return The mute state for the given audio volume group id. * @see #adjustVolumeGroupVolume(int, int, int) */ public boolean isVolumeGroupMuted(int groupId) { diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 8c635807022bdaa17be4ff93224033df402fd035..21690904fe42094e0970a1551dc7504850b0abf7 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -463,7 +463,7 @@ public final class MediaRouter2 { /** * Returns the current {@link RouteListingPreference} of the target router. * - * <p>If this instance was created using {@link #getInstance(Context, String)}, then it returns + * <p>If this instance was created using {@code #getInstance(Context, String)}, then it returns * the last {@link RouteListingPreference} set by the process this router was created for. * * @see #setRouteListingPreference(RouteListingPreference) diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig index a354f9180e09904916f0d6bedcf31cf43b563ade..c9cfa670cf02b8c25cb3f777cd4d7e4439a31a94 100644 --- a/media/java/android/media/flags/media_better_together.aconfig +++ b/media/java/android/media/flags/media_better_together.aconfig @@ -27,3 +27,10 @@ flag { description: "Disables the broadcast receiver that prevents scanning when the screen is off." bug: "304234628" } + +flag { + namespace: "media_solutions" + name: "fallback_to_default_handling_when_media_session_has_fixed_volume_handling" + description: "Fallbacks to the default handling for volume adjustment when media session has fixed volume handling and its app is in the foreground and setting a media controller." + bug: "293743975" +} diff --git a/media/java/android/media/midi/MidiUmpDeviceService.java b/media/java/android/media/midi/MidiUmpDeviceService.java index 6e2aaabf4b04c055fbaaa846b7ac785ea3410737..bbbe7f683b05e105defd3777abd4ac65aaf6de4a 100644 --- a/media/java/android/media/midi/MidiUmpDeviceService.java +++ b/media/java/android/media/midi/MidiUmpDeviceService.java @@ -38,7 +38,7 @@ import java.util.List; * of {@link MidiReceiver}s for sending data out the output ports. * * Unlike traditional MIDI byte streams, only complete UMPs should be sent. - * Unlike with {@link #MidiDeviceService}, the number of input and output ports must be equal. + * Unlike with {@link MidiDeviceService}, the number of input and output ports must be equal. * * <p>To extend this class, you must declare the service in your manifest file with * an intent filter with the {@link #SERVICE_INTERFACE} action diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl index d294601b44ccdd80513a751e8c8c05a0bb22dbdd..80e22477efeddd6730eebe925aa7835c06c38b28 100644 --- a/media/java/android/media/projection/IMediaProjectionManager.aidl +++ b/media/java/android/media/projection/IMediaProjectionManager.aidl @@ -156,4 +156,24 @@ interface IMediaProjectionManager { + ".permission.MANAGE_MEDIA_PROJECTION)") void setUserReviewGrantedConsentResult(ReviewGrantedConsentResult consentResult, in @nullable IMediaProjection projection); + + /** + * Notifies system server that we are handling a particular state during the consent flow. + * + * <p>Only used for emitting atoms. + * + * @param hostUid The uid of the process requesting consent to capture, may be an app or + * SystemUI. + * @param state The state that SystemUI is handling during the consent flow. + * Must be a valid + * state defined in the MediaProjectionState enum. + * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED. + * Indicates the entry point for requesting the permission. Must be + * a valid state defined + * in the SessionCreationSource enum. + */ + @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION") + @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + + ".permission.MANAGE_MEDIA_PROJECTION)") + void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource); } diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java index 13f7743f8d262801f1e9cd972f109714da61cdce..2db4be86bf9179d4e3e427c841db538289bc62ad 100644 --- a/media/java/android/media/tv/TvInputInfo.java +++ b/media/java/android/media/tv/TvInputInfo.java @@ -940,9 +940,7 @@ public final class TvInputInfo implements Parcelable { isHardwareInput = true; hdmiConnectionRelativePosition = getRelativePosition(mContext, mHdmiDeviceInfo); isConnectedToHdmiSwitch = hdmiConnectionRelativePosition - != HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW - && hdmiConnectionRelativePosition - != HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN; + == HdmiUtils.HDMI_RELATIVE_POSITION_BELOW; } else if (mTvInputHardwareInfo != null) { id = generateInputId(componentName, mTvInputHardwareInfo); type = sHardwareTypeToTvInputType.get(mTvInputHardwareInfo.getType(), TYPE_TUNER); diff --git a/omapi/aidl/Android.bp b/omapi/aidl/Android.bp index 58bcd1d49c691a6fb3a90f8cb054fbc72887f7e2..e71597a27a398e682e5474db859380696ac6093f 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/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 4c313b22f71e3b87f83e2be77e238726a58010dc..3409c29d3c2c806824b0926ffa2f0ae976fafed2 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -16,6 +16,8 @@ package com.android.externalstorage; +import static java.util.regex.Pattern.CASE_INSENSITIVE; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.usage.StorageStatsManager; @@ -64,7 +66,19 @@ import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.UUID; - +import java.util.regex.Pattern; + +/** + * Presents content of the shared (a.k.a. "external") storage. + * <p> + * Starting with Android 11 (R), restricts access to the certain sections of the shared storage: + * {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/}, that will be hidden in + * the DocumentsUI by default. + * See <a href="https://developer.android.com/about/versions/11/privacy/storage"> + * Storage updates in Android 11</a>. + * <p> + * Documents ID format: {@code root:path/to/file}. + */ public class ExternalStorageProvider extends FileSystemProvider { private static final String TAG = "ExternalStorage"; @@ -75,7 +89,12 @@ public class ExternalStorageProvider extends FileSystemProvider { private static final Uri BASE_URI = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build(); - // docId format: root:path/to/file + /** + * Regex for detecting {@code /Android/data/}, {@code /Android/obb/} and + * {@code /Android/sandbox/} along with all their subdirectories and content. + */ + private static final Pattern PATTERN_RESTRICTED_ANDROID_SUBTREES = + Pattern.compile("^Android/(?:data|obb|sandbox)(?:/.+)?", CASE_INSENSITIVE); private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, @@ -278,76 +297,91 @@ public class ExternalStorageProvider extends FileSystemProvider { return projection != null ? projection : DEFAULT_ROOT_PROJECTION; } + /** + * Mark {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/} on the + * integrated shared ("external") storage along with all their content and subdirectories as + * hidden. + */ @Override - public Cursor queryChildDocumentsForManage( - String parentDocId, String[] projection, String sortOrder) - throws FileNotFoundException { - return queryChildDocumentsShowAll(parentDocId, projection, sortOrder); + protected boolean shouldHideDocument(@NonNull String documentId) { + // Don't need to hide anything on USB drives. + if (isOnRemovableUsbStorage(documentId)) { + return false; + } + + final String path = getPathFromDocId(documentId); + return PATTERN_RESTRICTED_ANDROID_SUBTREES.matcher(path).matches(); } /** * Check that the directory is the root of storage or blocked file from tree. + * <p> + * Note, that this is different from hidden documents: blocked documents <b>WILL</b> appear + * the UI, but the user <b>WILL NOT</b> be able to select them. * - * @param docId the docId of the directory to be checked + * @param documentId the docId of the directory to be checked * @return true, should be blocked from tree. Otherwise, false. + * + * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE */ @Override - protected boolean shouldBlockFromTree(@NonNull String docId) { - try { - final File dir = getFileForDocId(docId, false /* visible */); - - // the file is null or it is not a directory - if (dir == null || !dir.isDirectory()) { - return false; - } + protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId) + throws FileNotFoundException { + final File dir = getFileForDocId(documentId, false); + // The file is null or it is not a directory + if (dir == null || !dir.isDirectory()) { + return false; + } - // Allow all directories on USB, including the root. - try { - RootInfo rootInfo = getRootFromDocId(docId); - if ((rootInfo.flags & Root.FLAG_REMOVABLE_USB) == Root.FLAG_REMOVABLE_USB) { - return false; - } - } catch (FileNotFoundException e) { - Log.e(TAG, "Failed to determine rootInfo for docId"); - } + // Allow all directories on USB, including the root. + if (isOnRemovableUsbStorage(documentId)) { + return false; + } - final String path = getPathFromDocId(docId); + // Get canonical(!) path. Note that this path will have neither leading nor training "/". + // This the root's path will be just an empty string. + final String path = getPathFromDocId(documentId); - // Block the root of the storage - if (path.isEmpty()) { - return true; - } + // Block the root of the storage + if (path.isEmpty()) { + return true; + } - // Block Download folder from tree - if (TextUtils.equals(Environment.DIRECTORY_DOWNLOADS.toLowerCase(Locale.ROOT), - path.toLowerCase(Locale.ROOT))) { - return true; - } + // Block /Download/ and /Android/ folders from the tree. + if (equalIgnoringCase(path, Environment.DIRECTORY_DOWNLOADS) || + equalIgnoringCase(path, Environment.DIRECTORY_ANDROID)) { + return true; + } - // Block /Android - if (TextUtils.equals(Environment.DIRECTORY_ANDROID.toLowerCase(Locale.ROOT), - path.toLowerCase(Locale.ROOT))) { - return true; - } + // This shouldn't really make a difference, but just in case - let's block hidden + // directories as well. + if (shouldHideDocument(documentId)) { + return true; + } - // Block /Android/data, /Android/obb, /Android/sandbox and sub dirs - if (shouldHide(dir)) { - return true; - } + return false; + } + private boolean isOnRemovableUsbStorage(@NonNull String documentId) { + final RootInfo rootInfo; + try { + rootInfo = getRootFromDocId(documentId); + } catch (FileNotFoundException e) { + Log.e(TAG, "Failed to determine rootInfo for docId\"" + documentId + '"'); return false; - } catch (IOException e) { - throw new IllegalArgumentException( - "Failed to determine if " + docId + " should block from tree " + ": " + e); } + + return (rootInfo.flags & Root.FLAG_REMOVABLE_USB) != 0; } + @NonNull @Override - protected String getDocIdForFile(File file) throws FileNotFoundException { + protected String getDocIdForFile(@NonNull File file) throws FileNotFoundException { return getDocIdForFileMaybeCreate(file, false); } - private String getDocIdForFileMaybeCreate(File file, boolean createNewDir) + @NonNull + private String getDocIdForFileMaybeCreate(@NonNull File file, boolean createNewDir) throws FileNotFoundException { String path = file.getAbsolutePath(); @@ -417,31 +451,33 @@ public class ExternalStorageProvider extends FileSystemProvider { private File getFileForDocId(String docId, boolean visible, boolean mustExist) throws FileNotFoundException { RootInfo root = getRootFromDocId(docId); - return buildFile(root, docId, visible, mustExist); + return buildFile(root, docId, mustExist); } - private Pair<RootInfo, File> resolveDocId(String docId, boolean visible) - throws FileNotFoundException { + private Pair<RootInfo, File> resolveDocId(String docId) throws FileNotFoundException { RootInfo root = getRootFromDocId(docId); - return Pair.create(root, buildFile(root, docId, visible, true)); + return Pair.create(root, buildFile(root, docId, /* mustExist */ true)); } @VisibleForTesting - static String getPathFromDocId(String docId) throws IOException { + static String getPathFromDocId(String docId) { final int splitIndex = docId.indexOf(':', 1); final String docIdPath = docId.substring(splitIndex + 1); - // Get CanonicalPath and remove the first "/" - final String canonicalPath = new File(docIdPath).getCanonicalPath().substring(1); - if (canonicalPath.isEmpty()) { - return canonicalPath; + // Canonicalize path and strip the leading "/" + final String path; + try { + path = new File(docIdPath).getCanonicalPath().substring(1); + } catch (IOException e) { + Log.w(TAG, "Could not canonicalize \"" + docIdPath + '"'); + return ""; } - // remove trailing "/" - if (canonicalPath.charAt(canonicalPath.length() - 1) == '/') { - return canonicalPath.substring(0, canonicalPath.length() - 1); + // Remove the trailing "/" as well. + if (!path.isEmpty() && path.charAt(path.length() - 1) == '/') { + return path.substring(0, path.length() - 1); } else { - return canonicalPath; + return path; } } @@ -460,7 +496,7 @@ public class ExternalStorageProvider extends FileSystemProvider { return root; } - private File buildFile(RootInfo root, String docId, boolean visible, boolean mustExist) + private File buildFile(RootInfo root, String docId, boolean mustExist) throws FileNotFoundException { final int splitIndex = docId.indexOf(':', 1); final String path = docId.substring(splitIndex + 1); @@ -544,7 +580,7 @@ public class ExternalStorageProvider extends FileSystemProvider { @Override public Path findDocumentPath(@Nullable String parentDocId, String childDocId) throws FileNotFoundException { - final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId, false); + final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId); final RootInfo root = resolvedDocId.first; File child = resolvedDocId.second; @@ -648,6 +684,13 @@ public class ExternalStorageProvider extends FileSystemProvider { } } + /** + * Print the state into the given stream. + * Gets invoked when you run: + * <pre> + * adb shell dumpsys activity provider com.android.externalstorage/.ExternalStorageProvider + * </pre> + */ @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ", 160); @@ -731,4 +774,8 @@ public class ExternalStorageProvider extends FileSystemProvider { } return bundle; } + + private static boolean equalIgnoringCase(@NonNull String a, @NonNull String b) { + return TextUtils.equals(a.toLowerCase(Locale.ROOT), b.toLowerCase(Locale.ROOT)); + } } diff --git a/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java index 18a8edc5e447b157bd52c3701d83709133444148..0144b6ea9040c9f8772d637bc269eadeec64bf48 100644 --- a/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java +++ b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java @@ -16,47 +16,64 @@ package com.android.externalstorage; +import static android.provider.DocumentsContract.EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID; + import static com.android.externalstorage.ExternalStorageProvider.AUTHORITY; import static com.android.externalstorage.ExternalStorageProvider.getPathFromDocId; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import android.app.Instrumentation; +import android.content.Context; import android.content.pm.ProviderInfo; +import androidx.annotation.NonNull; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class ExternalStorageProviderTest { + + @NonNull + private static final Instrumentation sInstrumentation = + InstrumentationRegistry.getInstrumentation(); + @NonNull + private static final Context sTargetContext = sInstrumentation.getTargetContext(); + + private ExternalStorageProvider mExternalStorageProvider; + + @Before + public void setUp() { + mExternalStorageProvider = new ExternalStorageProvider(); + } + + @Test - public void onCreate_shouldUpdateVolumes() throws Exception { - ExternalStorageProvider externalStorageProvider = new ExternalStorageProvider(); - ExternalStorageProvider spyProvider = spy(externalStorageProvider); - ProviderInfo providerInfo = new ProviderInfo(); + public void onCreate_shouldUpdateVolumes() { + final ExternalStorageProvider spyProvider = spy(mExternalStorageProvider); + + final ProviderInfo providerInfo = new ProviderInfo(); providerInfo.authority = AUTHORITY; providerInfo.grantUriPermissions = true; providerInfo.exported = true; - InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { - @Override - public void run() { - spyProvider.attachInfoForTesting( - InstrumentationRegistry.getTargetContext(), providerInfo); - } - }); + sInstrumentation.runOnMainSync(() -> + spyProvider.attachInfoForTesting(sTargetContext, providerInfo)); verify(spyProvider, atLeast(1)).updateVolumes(); } @Test - public void testGetPathFromDocId() throws Exception { + public void test_getPathFromDocId() { final String root = "root"; final String path = "abc/def/ghi"; String docId = root + ":" + path; @@ -79,4 +96,62 @@ public class ExternalStorageProviderTest { docId = root + ":" + twoDotPath; assertEquals(getPathFromDocId(docId), path); } + + @Test + public void test_shouldHideDocument() { + // Should hide "Android/data", "Android/obb", "Android/sandbox" and all their + // "subtrees". + final String[] shouldHide = { + // "Android/data" and all its subdirectories + "Android/data", + "Android/data/com.my.app", + "Android/data/com.my.app/cache", + "Android/data/com.my.app/cache/image.png", + "Android/data/mydata", + + // "Android/obb" and all its subdirectories + "Android/obb", + "Android/obb/com.my.app", + "Android/obb/com.my.app/file.blob", + + // "Android/sandbox" and all its subdirectories + "Android/sandbox", + "Android/sandbox/com.my.app", + + // Also make sure we are not allowing path traversals + "Android/./data", + "Android/Download/../data", + }; + for (String path : shouldHide) { + final String docId = buildDocId(path); + assertTrue("ExternalStorageProvider should hide \"" + docId + "\", but it didn't", + mExternalStorageProvider.shouldHideDocument(docId)); + } + + // Should NOT hide anything else. + final String[] shouldNotHide = { + "Android", + "Android/datadir", + "Documents", + "Download", + "Music", + "Pictures", + }; + for (String path : shouldNotHide) { + final String docId = buildDocId(path); + assertFalse("ExternalStorageProvider should NOT hide \"" + docId + "\", but it did", + mExternalStorageProvider.shouldHideDocument(docId)); + } + } + + @NonNull + private static String buildDocId(@NonNull String path) { + return buildDocId(EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID, path); + } + + @NonNull + private static String buildDocId(@NonNull String root, @NonNull String path) { + // docId format: root:path/to/file + return root + ':' + path; + } } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java index 8de12d03a92abb47ca217652d89e092e9250a7b8..976a3ad69901fdc2cb5ab533c93156754766cdae 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java @@ -172,7 +172,7 @@ public class PackageUtil { private Bitmap getBitmapFromDrawable(Drawable drawable) { // Create an empty bitmap with the dimensions of our drawable - Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(), + final Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); // Associate it with a canvas. This canvas will draw the icon on the bitmap @@ -183,7 +183,11 @@ public class PackageUtil { // Scale it down if the icon is too large if ((bmp.getWidth() > iconSize * 2) || (bmp.getHeight() > iconSize * 2)) { - bmp = Bitmap.createScaledBitmap(bmp, iconSize, iconSize, true); + Bitmap scaledBitmap = Bitmap.createScaledBitmap(bmp, iconSize, iconSize, true); + if (scaledBitmap != bmp) { + bmp.recycle(); + } + return scaledBitmap; } return bmp; } diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index 1bb00b36f3152993c3796134495eefc9c4140466..ce0772ff84a19bc92da1905093735bacf1e9a984 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -54,6 +54,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.UserIcons; import com.android.launcher3.icons.BaseIconFactory.IconOptions; import com.android.launcher3.icons.IconFactory; +import com.android.launcher3.util.UserIconInfo; import com.android.settingslib.drawable.UserIconDrawable; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.settingslib.utils.BuildCompatUtils; @@ -597,15 +598,25 @@ public class Utils { /** Get the corresponding adaptive icon drawable. */ public static Drawable getBadgedIcon(Context context, Drawable icon, UserHandle user) { - UserManager um = context.getSystemService(UserManager.class); - boolean isClone = um.getProfiles(user.getIdentifier()).stream() - .anyMatch(profile -> - profile.isCloneProfile() && profile.id == user.getIdentifier()); + int userType = UserIconInfo.TYPE_MAIN; + try { + UserInfo ui = context.getSystemService(UserManager.class).getUserInfo( + user.getIdentifier()); + if (ui != null) { + if (ui.isCloneProfile()) { + userType = UserIconInfo.TYPE_CLONED; + } else if (ui.isManagedProfile()) { + userType = UserIconInfo.TYPE_WORK; + } + } + } catch (Exception e) { + // Ignore + } try (IconFactory iconFactory = IconFactory.obtain(context)) { return iconFactory .createBadgedIconBitmap( icon, - new IconOptions().setUser(user).setIsCloneProfile(isClone)) + new IconOptions().setUser(new UserIconInfo(user, userType))) .newIcon(context); } } diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index f65f5a3c929770f9187d05947feeddd2b8895b5e..4954cb4821200181229f7552205c17aef623ec14 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -62,6 +62,7 @@ systemui_compose_java_defaults { static_libs: [ "CommunalLayoutLib", "PlatformComposeCore", + "PlatformComposeSceneTransitionLayout", "androidx.compose.runtime_runtime", "androidx.compose.material3_material3", @@ -164,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/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 9bfc4be0f30d7e5fedf8f6f1d2bc18c00019352d..58816310d495ea2ab936deb713b5becaa57ba7c3 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -83,6 +83,7 @@ <uses-permission android:name="android.permission.PEERS_MAC_ADDRESS"/> <uses-permission android:name="android.permission.READ_WIFI_CREDENTIAL"/> <uses-permission android:name="android.permission.LOCATION_HARDWARE" /> + <uses-permission android:name="android.permission.NETWORK_FACTORY" /> <!-- Physical hardware --> <uses-permission android:name="android.permission.MANAGE_USB" /> <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" /> @@ -1062,5 +1063,9 @@ <meta-data android:name="androidx.emoji2.text.EmojiCompatInitializer" tools:node="remove" /> </provider> + + <!-- Allow SystemUI to listen for the capabilities defined in the linked xml --> + <property android:name="android.net.PROPERTY_SELF_CERTIFIED_CAPABILITIES" + android:value="@xml/self_certified_network_capabilities_both" /> </application> </manifest> diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp index b18c7900a1686f648976ab404961da4b3d289a6e..dc4208e8720741d17b48a54139cc456c8617a800 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 8ecb9842ddc52a5440e4407a164b69cde853bbcb..2c6ff979cc7ff05a8d81b7137e9dc7e926f1ad10 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/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp index e4426fe9785903edbc75713cbb408f14835403e1..796abf4b52d6fb1b98c92543d9f23d230a11432b 100644 --- a/packages/SystemUI/compose/features/Android.bp +++ b/packages/SystemUI/compose/features/Android.bp @@ -33,6 +33,7 @@ android_library { static_libs: [ "SystemUI-core", "PlatformComposeCore", + "PlatformComposeSceneTransitionLayout", "androidx.compose.runtime_runtime", "androidx.compose.animation_animation-graphics", diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt index 591fa76f423f15b0ef2dba519af2e82072ca7fd3..4bbb78b69392cf1455ced3f4258fc3291377eac7 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt @@ -371,7 +371,8 @@ private fun StatusIcons( val iconContainer = StatusIconContainer(context, null) val iconManager = createTintedIconManager(iconContainer, StatusBarLocation.QS) iconManager.setTint( - Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) + Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary), + Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimaryInverse), ) statusBarIconController.addIconGroup(iconManager) diff --git a/packages/SystemUI/compose/scene/Android.bp b/packages/SystemUI/compose/scene/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..050d1d5651add1b00fb670ac868190af14f100f8 --- /dev/null +++ b/packages/SystemUI/compose/scene/Android.bp @@ -0,0 +1,39 @@ +// 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +android_library { + name: "PlatformComposeSceneTransitionLayout", + manifest: "AndroidManifest.xml", + + srcs: [ + "src/**/*.kt", + ], + + static_libs: [ + "androidx.compose.runtime_runtime", + "androidx.compose.material3_material3", + ], + + kotlincflags: ["-Xjvm-default=all"], + use_resource_processor: true, +} diff --git a/packages/SystemUI/compose/scene/AndroidManifest.xml b/packages/SystemUI/compose/scene/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..81131bb689e4f1c54f25ee17b795a38a0cdec688 --- /dev/null +++ b/packages/SystemUI/compose/scene/AndroidManifest.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.compose.animation.scene"> + + +</manifest> diff --git a/packages/SystemUI/compose/scene/OWNERS b/packages/SystemUI/compose/scene/OWNERS new file mode 100644 index 0000000000000000000000000000000000000000..33a59c2bcab38ebf9299f80033fec68b2ac76297 --- /dev/null +++ b/packages/SystemUI/compose/scene/OWNERS @@ -0,0 +1,13 @@ +set noparent + +# Bug component: 1184816 + +jdemeulenaere@google.com +omarmt@google.com + +# SysUI Dr No's. +# Don't send reviews here. +dsandler@android.com +cinek@google.com +juliacr@google.com +pixel@google.com \ No newline at end of file diff --git a/packages/SystemUI/compose/scene/TEST_MAPPING b/packages/SystemUI/compose/scene/TEST_MAPPING new file mode 100644 index 0000000000000000000000000000000000000000..f644a23ba0a3ded9d9eec876b15dad2c515510b9 --- /dev/null +++ b/packages/SystemUI/compose/scene/TEST_MAPPING @@ -0,0 +1,48 @@ +{ + "presubmit": [ + { + "name": "PlatformComposeSceneTransitionLayoutTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "PlatformComposeCoreTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "SystemUIComposeFeaturesTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "SystemUIComposeGalleryTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ] +} \ No newline at end of file diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt similarity index 71% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt index 566967f920d349ed108171f211bd3943536dd88c..041fc48dd09e266d6e7ef8173a9fdce804c333cc 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt @@ -17,12 +17,10 @@ package com.android.compose.animation.scene import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.DisposableEffectResult -import androidx.compose.runtime.DisposableEffectScope import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.snapshots.Snapshot import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.lerp import androidx.compose.ui.unit.Dp @@ -44,6 +42,20 @@ fun SceneScope.animateSharedIntAsState( return animateSharedValueAsState(value, key, element, ::lerp, canOverflow) } +/** + * Animate a shared Int value. + * + * @see MovableElementScope.animateSharedValueAsState + */ +@Composable +fun MovableElementScope.animateSharedIntAsState( + value: Int, + debugName: String, + canOverflow: Boolean = true, +): State<Int> { + return animateSharedValueAsState(value, debugName, ::lerp, canOverflow) +} + /** * Animate a shared Float value. * @@ -59,6 +71,20 @@ fun SceneScope.animateSharedFloatAsState( return animateSharedValueAsState(value, key, element, ::lerp, canOverflow) } +/** + * Animate a shared Float value. + * + * @see MovableElementScope.animateSharedValueAsState + */ +@Composable +fun MovableElementScope.animateSharedFloatAsState( + value: Float, + debugName: String, + canOverflow: Boolean = true, +): State<Float> { + return animateSharedValueAsState(value, debugName, ::lerp, canOverflow) +} + /** * Animate a shared Dp value. * @@ -74,6 +100,20 @@ fun SceneScope.animateSharedDpAsState( return animateSharedValueAsState(value, key, element, ::lerp, canOverflow) } +/** + * Animate a shared Dp value. + * + * @see MovableElementScope.animateSharedValueAsState + */ +@Composable +fun MovableElementScope.animateSharedDpAsState( + value: Dp, + debugName: String, + canOverflow: Boolean = true, +): State<Dp> { + return animateSharedValueAsState(value, debugName, ::lerp, canOverflow) +} + /** * Animate a shared Color value. * @@ -88,6 +128,19 @@ fun SceneScope.animateSharedColorAsState( return animateSharedValueAsState(value, key, element, ::lerp, canOverflow = false) } +/** + * Animate a shared Color value. + * + * @see MovableElementScope.animateSharedValueAsState + */ +@Composable +fun MovableElementScope.animateSharedColorAsState( + value: Color, + debugName: String, +): State<Color> { + return animateSharedValueAsState(value, debugName, ::lerp, canOverflow = false) +} + @Composable internal fun <T> animateSharedValueAsState( layoutImpl: SceneTransitionLayoutImpl, @@ -98,33 +151,22 @@ internal fun <T> animateSharedValueAsState( lerp: (T, T, Float) -> T, canOverflow: Boolean, ): State<T> { - val sharedValue = remember(key) { Element.SharedValue(key, value) } + val sharedValue = + Snapshot.withoutReadObservation { + element.sceneValues.getValue(scene.key).sharedValues.getOrPut(key) { + Element.SharedValue(key, value) + } as Element.SharedValue<T> + } + if (value != sharedValue.value) { sharedValue.value = value } - DisposableEffect(element, scene, sharedValue) { - addSharedValueToElement(element, scene, sharedValue) - } - return remember(layoutImpl, element, sharedValue, lerp, canOverflow) { derivedStateOf { computeValue(layoutImpl, element, sharedValue, lerp, canOverflow) } } } -private fun <T> DisposableEffectScope.addSharedValueToElement( - element: Element, - scene: Scene, - sharedValue: Element.SharedValue<T>, -): DisposableEffectResult { - val sceneValues = - element.sceneValues[scene.key] ?: error("Element $element is not present in $scene") - val sharedValues = sceneValues.sharedValues - - sharedValues[sharedValue.key] = sharedValue - return onDispose { sharedValues.remove(sharedValue.key) } -} - private fun <T> computeValue( layoutImpl: SceneTransitionLayoutImpl, element: Element, diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt similarity index 89% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/MovableElement.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt index 11bbf2aa987e9c03436cfba90ba22ac9e69a33d8..6dbeb69ff450a76c03be5dbda8b558ef04ba4fb9 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/MovableElement.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt @@ -21,6 +21,7 @@ import android.util.Log import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.runtime.Composable +import androidx.compose.runtime.State import androidx.compose.runtime.remember import androidx.compose.runtime.snapshots.Snapshot import androidx.compose.ui.Modifier @@ -36,8 +37,6 @@ import androidx.compose.ui.unit.IntSize private const val TAG = "MovableElement" -private object MovableElementScopeImpl : MovableElementScope - @Composable internal fun MovableElement( layoutImpl: SceneTransitionLayoutImpl, @@ -51,6 +50,10 @@ internal fun MovableElement( // every time an element is added/removed from SceneTransitionLayoutImpl.elements, so we // disable read observation during the look-up in that map. val element = Snapshot.withoutReadObservation { layoutImpl.elements.getValue(key) } + val movableElementScope = + remember(layoutImpl, element, scene) { + MovableElementScopeImpl(layoutImpl, element, scene) + } // The [Picture] to which we save the last drawing commands of this element. This is // necessary because the content of this element might not be composed in this scene, in @@ -77,7 +80,7 @@ internal fun MovableElement( } } ) { - element.movableContent { MovableElementScopeImpl.content() } + element.movableContent { movableElementScope.content() } } } else { // If we are not composed, we draw the previous drawing commands at the same size as the @@ -178,3 +181,20 @@ private fun shouldComposeMovableElement( isHighestScene } } + +private class MovableElementScopeImpl( + private val layoutImpl: SceneTransitionLayoutImpl, + private val element: Element, + private val scene: Scene, +) : MovableElementScope { + @Composable + override fun <T> animateSharedValueAsState( + value: T, + debugName: String, + lerp: (start: T, stop: T, fraction: Float) -> T, + canOverflow: Boolean, + ): State<T> { + val key = remember { ValueKey(debugName) } + return animateSharedValueAsState(layoutImpl, scene, element, key, value, lerp, canOverflow) + } +} diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt similarity index 96% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index 4283c0e61df884c53c54be5a14d6edd9caf1b6e3..74e66d2a99494ef7e9fd85dc631f3751f34fd061 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -160,7 +160,16 @@ interface SceneScope { // TODO(b/291053742): Add animateSharedValueAsState(targetValue) without any ValueKey and ElementKey // arguments to allow sharing values inside a movable element. -@ElementDsl interface MovableElementScope +@ElementDsl +interface MovableElementScope { + @Composable + fun <T> animateSharedValueAsState( + value: T, + debugName: String, + lerp: (start: T, stop: T, fraction: Float) -> T, + canOverflow: Boolean, + ): State<T> +} /** An action performed by the user. */ sealed interface UserAction diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt b/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/modifiers/ConditionalModifiers.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/modifiers/ConditionalModifiers.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/ListUtils.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/ui/util/ListUtils.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt similarity index 100% rename from packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt rename to packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt diff --git a/packages/SystemUI/compose/scene/tests/Android.bp b/packages/SystemUI/compose/scene/tests/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..b53fae24865c8d9e530d5e9b8dfe9820a784b1d0 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/Android.bp @@ -0,0 +1,50 @@ +// 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +android_test { + name: "PlatformComposeSceneTransitionLayoutTests", + manifest: "AndroidManifest.xml", + test_suites: ["device-tests"], + sdk_version: "current", + certificate: "platform", + + srcs: [ + "src/**/*.kt", + ], + + static_libs: [ + "PlatformComposeSceneTransitionLayout", + + "androidx.test.runner", + "androidx.test.ext.junit", + + "androidx.compose.runtime_runtime", + "androidx.compose.ui_ui-test-junit4", + "androidx.compose.ui_ui-test-manifest", + + "truth", + ], + + kotlincflags: ["-Xjvm-default=all"], + use_resource_processor: true, +} diff --git a/packages/SystemUI/compose/scene/tests/AndroidManifest.xml b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..1a9172ee20e0409af531a72e2b7822a5a822630a --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.compose.animation.scene.tests" > + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.compose.animation.scene.tests" + android:label="Tests for SceneTransitionLayout"/> + +</manifest> \ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..7b7695eebd2fbbc31f2b26feb9ee761619c643d0 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt @@ -0,0 +1,218 @@ +/* + * 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.compose.animation.scene + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.Box +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.lerp +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.lerp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.ui.util.lerp +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class AnimatedSharedAsStateTest { + @get:Rule val rule = createComposeRule() + + private data class Values( + val int: Int, + val float: Float, + val dp: Dp, + val color: Color, + ) + + private fun lerp(start: Values, stop: Values, fraction: Float): Values { + return Values( + int = lerp(start.int, stop.int, fraction), + float = lerp(start.float, stop.float, fraction), + dp = lerp(start.dp, stop.dp, fraction), + color = lerp(start.color, stop.color, fraction), + ) + } + + @Composable + private fun SceneScope.Foo( + targetValues: Values, + onCurrentValueChanged: (Values) -> Unit, + ) { + val key = TestElements.Foo + Box(Modifier.element(key)) { + val int by animateSharedIntAsState(targetValues.int, TestValues.Value1, key) + val float by animateSharedFloatAsState(targetValues.float, TestValues.Value2, key) + val dp by animateSharedDpAsState(targetValues.dp, TestValues.Value3, key) + val color by animateSharedColorAsState(targetValues.color, TestValues.Value4, key) + + // Make sure we read the values during composition, so that we recompose and call + // onCurrentValueChanged() with the latest values. + val currentValues = Values(int, float, dp, color) + SideEffect { onCurrentValueChanged(currentValues) } + } + } + + @Composable + private fun SceneScope.MovableFoo( + targetValues: Values, + onCurrentValueChanged: (Values) -> Unit, + ) { + val key = TestElements.Foo + MovableElement(key = key, Modifier) { + val int by + animateSharedIntAsState(targetValues.int, debugName = TestValues.Value1.debugName) + val float by + animateSharedFloatAsState( + targetValues.float, + debugName = TestValues.Value2.debugName + ) + val dp by + animateSharedDpAsState(targetValues.dp, debugName = TestValues.Value3.debugName) + val color by + animateSharedColorAsState( + targetValues.color, + debugName = TestValues.Value4.debugName + ) + + // Make sure we read the values during composition, so that we recompose and call + // onCurrentValueChanged() with the latest values. + val currentValues = Values(int, float, dp, color) + SideEffect { onCurrentValueChanged(currentValues) } + } + } + + @Test + fun animateSharedValues() { + val fromValues = Values(int = 0, float = 0f, dp = 0.dp, color = Color.Red) + val toValues = Values(int = 100, float = 100f, dp = 100.dp, color = Color.Blue) + + var lastValueInFrom = fromValues + var lastValueInTo = toValues + + rule.testTransition( + fromSceneContent = { + Foo(targetValues = fromValues, onCurrentValueChanged = { lastValueInFrom = it }) + }, + toSceneContent = { + Foo(targetValues = toValues, onCurrentValueChanged = { lastValueInTo = it }) + }, + transition = { + // The transition lasts 64ms = 4 frames. + spec = tween(durationMillis = 16 * 4, easing = LinearEasing) + }, + fromScene = TestScenes.SceneA, + toScene = TestScenes.SceneB, + ) { + before { + assertThat(lastValueInFrom).isEqualTo(fromValues) + + // to was not composed yet, so lastValueInTo was not set yet. + assertThat(lastValueInTo).isEqualTo(toValues) + } + + at(16) { + // Given that we use Modifier.element() here, animateSharedXAsState is composed in + // both scenes and values should be interpolated with the transition fraction. + val expectedValues = lerp(fromValues, toValues, fraction = 0.25f) + assertThat(lastValueInFrom).isEqualTo(expectedValues) + assertThat(lastValueInTo).isEqualTo(expectedValues) + } + + at(32) { + val expectedValues = lerp(fromValues, toValues, fraction = 0.5f) + assertThat(lastValueInFrom).isEqualTo(expectedValues) + assertThat(lastValueInTo).isEqualTo(expectedValues) + } + + at(48) { + val expectedValues = lerp(fromValues, toValues, fraction = 0.75f) + assertThat(lastValueInFrom).isEqualTo(expectedValues) + assertThat(lastValueInTo).isEqualTo(expectedValues) + } + + after { + assertThat(lastValueInFrom).isEqualTo(toValues) + assertThat(lastValueInTo).isEqualTo(toValues) + } + } + } + + @Test + fun movableAnimateSharedValues() { + val fromValues = Values(int = 0, float = 0f, dp = 0.dp, color = Color.Red) + val toValues = Values(int = 100, float = 100f, dp = 100.dp, color = Color.Blue) + + var lastValueInFrom = fromValues + var lastValueInTo = toValues + + rule.testTransition( + fromSceneContent = { + MovableFoo( + targetValues = fromValues, + onCurrentValueChanged = { lastValueInFrom = it } + ) + }, + toSceneContent = { + MovableFoo(targetValues = toValues, onCurrentValueChanged = { lastValueInTo = it }) + }, + transition = { + // The transition lasts 64ms = 4 frames. + spec = tween(durationMillis = 16 * 4, easing = LinearEasing) + }, + fromScene = TestScenes.SceneA, + toScene = TestScenes.SceneB, + ) { + before { + assertThat(lastValueInFrom).isEqualTo(fromValues) + + // to was not composed yet, so lastValueInTo was not set yet. + assertThat(lastValueInTo).isEqualTo(toValues) + } + + at(16) { + // Given that we use MovableElement here, animateSharedXAsState is composed only + // once, in the highest scene (in this case, in toScene). + assertThat(lastValueInFrom).isEqualTo(fromValues) + assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.25f)) + } + + at(32) { + assertThat(lastValueInFrom).isEqualTo(fromValues) + assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.5f)) + } + + at(48) { + assertThat(lastValueInFrom).isEqualTo(fromValues) + assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.75f)) + } + + after { + assertThat(lastValueInFrom).isEqualTo(fromValues) + assertThat(lastValueInTo).isEqualTo(toValues) + } + } + } +} diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt similarity index 100% rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/MovableElementTest.kt rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt similarity index 100% rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt similarity index 100% rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt similarity index 100% rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestTransition.kt similarity index 100% rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestTransition.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestValues.kt similarity index 95% rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestValues.kt index 83572620c88abfb440a00a19392b70df536f4874..b4c393e9bfbed726f93c3867062eeedaf280f5e6 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestValues.kt @@ -37,6 +37,9 @@ object TestElements { /** Value keys that can be reused by tests. */ object TestValues { val Value1 = ValueKey("Value1") + val Value2 = ValueKey("Value2") + val Value3 = ValueKey("Value3") + val Value4 = ValueKey("Value4") } // We use a transition duration of 480ms here because it is a multiple of 16, the time of a frame in diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt similarity index 100% rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt similarity index 100% rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt similarity index 100% rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt similarity index 100% rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt similarity index 100% rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt similarity index 99% rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt index 2af3638602720ca5fc5e12d3ff4d3e728a847776..e94eff32c30c1c1623560e2d05ba24355a045c62 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt @@ -32,7 +32,6 @@ import com.android.compose.animation.scene.TestElements import com.android.compose.animation.scene.TestScenes import com.android.compose.animation.scene.inScene import com.android.compose.animation.scene.testTransition -import com.android.compose.modifiers.size import com.android.compose.test.assertSizeIsEqualTo import com.android.compose.test.onEach import org.junit.Rule diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt similarity index 100% rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt similarity index 100% rename from packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt similarity index 100% rename from packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/Selectors.kt similarity index 100% rename from packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/test/Selectors.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SizeAssertions.kt similarity index 100% rename from packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SizeAssertions.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt similarity index 100% rename from packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java index a5e5aaa499f1310843b9ae1941ea3b109e529f58..a9d2ee3cdfe6b99fbd4ef05f82bd024f8dc8b351 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java @@ -71,7 +71,11 @@ public interface DarkIconDispatcher { */ void applyDark(DarkReceiver object); + /** The default tint (applicable for dark backgrounds) is white */ int DEFAULT_ICON_TINT = Color.WHITE; + /** To support an icon which wants to create contrast, the default tint is black-on-white. */ + int DEFAULT_INVERSE_ICON_TINT = Color.BLACK; + Rect sTmpRect = new Rect(); int[] sTmpInt2 = new int[2]; @@ -87,6 +91,18 @@ public interface DarkIconDispatcher { } } + /** + * @return the tint to apply to a foreground, given that the background is tinted + * per {@link #getTint} + */ + static int getInverseTint(Collection<Rect> tintAreas, View view, int inverseColor) { + if (isInAreas(tintAreas, view)) { + return inverseColor; + } else { + return DEFAULT_INVERSE_ICON_TINT; + } + } + /** * @return true if more than half of the view area are in any of the given * areas, false otherwise @@ -129,7 +145,40 @@ public interface DarkIconDispatcher { */ @ProvidesInterface(version = DarkReceiver.VERSION) interface DarkReceiver { - int VERSION = 2; + int VERSION = 3; + + /** + * @param areas list of regions on screen where the tint applies + * @param darkIntensity float representing the level of tint. In the range [0,1] + * @param tint the tint applicable as a foreground contrast to the dark regions. This value + * is interpolated between a default light and dark tone, and is therefore + * usable as-is, as long as the view is in one of the areas defined in + * {@code areas}. + * + * @see DarkIconDispatcher#isInArea(Rect, View) for utilizing {@code areas} + * + * Note: only one of {@link #onDarkChanged(ArrayList, float, int)} or + * {@link #onDarkChangedWithContrast(ArrayList, int, int)} need to be implemented, as both + * will be called in the same circumstances. + */ void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint); + + /** + * New version of onDarkChanged, which describes a tint plus an optional contrastTint + * that can be used if the tint is applied to the background of an icon. + * + * We use the 2 here to avoid the case where an existing override of onDarkChanged + * might pass in parameters as bare numbers (e.g. 0 instead of 0f) which might get + * mistakenly cast to (int) and therefore trigger this method. + * + * @param areas list of areas where dark tint applies + * @param tint int describing the tint color to use + * @param contrastTint if desired, a contrasting color that can be used for a foreground + * + * Note: only one of {@link #onDarkChanged(ArrayList, float, int)} or + * {@link #onDarkChangedWithContrast(ArrayList, int, int)} need to be implemented, as both + * will be called in the same circumstances. + */ + default void onDarkChangedWithContrast(ArrayList<Rect> areas, int tint, int contrastTint) {} } } diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml index a1e2dc36278b53a1b2bee7965fe69864fa9b3db6..a8017f65c810ccd131e576cd17cc4bed54224575 100644 --- a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml +++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml @@ -52,15 +52,22 @@ android:visibility="gone" /> </FrameLayout> - <ImageView - android:id="@+id/mobile_type" - android:layout_height="@dimen/status_bar_mobile_type_size" + <FrameLayout + android:id="@+id/mobile_type_container" + android:layout_height="@dimen/status_bar_mobile_container_height" android:layout_width="wrap_content" - android:layout_gravity="center_vertical" - android:adjustViewBounds="true" - android:paddingStart="2.5sp" - android:paddingEnd="1sp" - android:visibility="gone" /> + android:layout_marginStart="2.5sp" + android:layout_marginEnd="1sp" + android:visibility="gone" + > + <ImageView + android:id="@+id/mobile_type" + android:layout_height="@dimen/status_bar_mobile_type_size" + android:layout_width="wrap_content" + android:layout_gravity="center_vertical" + android:adjustViewBounds="true" + /> + </FrameLayout> <Space android:id="@+id/mobile_roaming_space" android:layout_height="match_parent" diff --git a/packages/SystemUI/res/drawable/mobile_network_type_background.xml b/packages/SystemUI/res/drawable/mobile_network_type_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..db25c89d1fa5af249ac3838056028da54e2aa755 --- /dev/null +++ b/packages/SystemUI/res/drawable/mobile_network_type_background.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ 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. + ~ + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle" + > + <corners + android:topLeftRadius="0dp" + android:topRightRadius="@dimen/status_bar_mobile_container_corner_radius" + android:bottomRightRadius="0dp" + android:bottomLeftRadius="@dimen/status_bar_mobile_container_corner_radius"/> + <solid android:color="#FFF" /> + <padding + android:left="2sp" + android:right="2sp"/> +</shape> diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml index a51c55ee965f47b6e6a138f3a05a395c64c50239..8cfcb689ecedcfac72bb7ea4e57c288b50ebd9ce 100644 --- a/packages/SystemUI/res/layout/connected_display_dialog.xml +++ b/packages/SystemUI/res/layout/connected_display_dialog.xml @@ -15,8 +15,9 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:id="@+id/cd_bottom_sheet" android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="wrap_content" android:gravity="center_horizontal" android:orientation="vertical" android:paddingHorizontal="@dimen/dialog_side_padding" @@ -26,11 +27,14 @@ <ImageView android:id="@+id/connected_display_dialog_icon" - android:layout_width="@dimen/screenrecord_logo_size" - android:layout_height="@dimen/screenrecord_logo_size" + android:layout_width="@dimen/connected_display_dialog_logo_size" + android:layout_height="@dimen/connected_display_dialog_logo_size" + android:background="@drawable/circular_background" + android:backgroundTint="?androidprv:attr/materialColorPrimary" android:importantForAccessibility="no" + android:padding="6dp" android:src="@drawable/stat_sys_connected_display" - android:tint="?androidprv:attr/materialColorPrimary" /> + android:tint="?androidprv:attr/materialColorOnPrimary" /> <TextView android:id="@+id/connected_display_dialog_title" diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml index ea3c012afc4315591711ceac66b48cd99776de20..1f671ac4c875bbcc165b9e4c68904f658e6d66da 100644 --- a/packages/SystemUI/res/values-sw600dp/config.xml +++ b/packages/SystemUI/res/values-sw600dp/config.xml @@ -37,6 +37,9 @@ <bool name="config_use_large_screen_shade_header">true</bool> + <!-- Whether to show bottom sheets edge to edge --> + <bool name="config_edgeToEdgeBottomSheetDialog">false</bool> + <!-- A collection of defaults for the quick affordances on the lock screen. Each item must be a string with two parts: the ID of the slot and the comma-delimited list of affordance IDs, separated by a colon ':' character. For example: <item>bottom_end:home,wallet</item>. The diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 1add90ff4083e814b6bf496a0d2852a6381b3799..75e71e414262435f417434ace60c7c6f709621df 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -947,6 +947,9 @@ <!-- Flag controlling whether visual query attention detection has been enabled. --> <bool name="config_enableVisualQueryAttentionDetection">false</bool> + <!-- Whether to show bottom sheets edge to edge --> + <bool name="config_edgeToEdgeBottomSheetDialog">true</bool> + <!-- Whether the scene container framework is enabled. @@ -954,4 +957,15 @@ bouncer, lockscreen, shade, and quick settings. --> <bool name="config_sceneContainerFrameworkEnabled">true</bool> + + <!-- + Time in milliseconds the user has to touch the side FPS sensor to successfully authenticate + TODO(b/302332976) Get this value from the HAL if they can provide an API for it. + --> + <integer name="config_restToUnlockDuration">300</integer> + + <!-- + Width in pixels of the Side FPS sensor. + --> + <integer name="config_sfpsSensorWidth">200</integer> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 5a83c7d2dc2aca136b2bb6f45b93cd9102b12061..1b09c6afeafc3dfec9f5125c5a180d5e5ff97435 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -177,6 +177,12 @@ <!-- Size of the view displaying the mobile signal icon in the status bar. This value should match the viewport height of mobile signal drawables such as ic_lte_mobiledata --> <dimen name="status_bar_mobile_type_size">16sp</dimen> + <!-- Size of the view that contains the network type. Should be equal to + status_bar_mobile_type_size + 2, to account for 1sp top and bottom padding --> + <dimen name="status_bar_mobile_container_height">18sp</dimen> + <!-- Corner radius for the background of the network type indicator. Should be equal to + status_bar_mobile_container_height / 2 --> + <dimen name="status_bar_mobile_container_corner_radius">9sp</dimen> <!-- Size of the view displaying the mobile roam icon in the status bar. This value should match the viewport size of drawable stat_sys_roaming --> <dimen name="status_bar_mobile_roam_size">8sp</dimen> @@ -1355,6 +1361,9 @@ <dimen name="screenrecord_options_padding_bottom">16dp</dimen> <dimen name="screenrecord_buttons_margin_top">20dp</dimen> + <!-- Connected display dialog --> + <dimen name="connected_display_dialog_logo_size">48dp</dimen> + <!-- Keyguard user switcher --> <dimen name="kg_user_switcher_text_size">16sp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 321594f4147948ebea0466ece1d775a4bb9c37f4..7e44adf8413729999ba1743488098fa025e1ec89 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -711,8 +711,8 @@ <!-- QuickSettings: Cast detail panel, default device description [CHAR LIMIT=NONE] --> <!-- QuickSettings: Cast detail panel, text when there are no items [CHAR LIMIT=NONE] --> <string name="quick_settings_cast_detail_empty_text">No devices available</string> - <!-- QuickSettings: Cast unavailable, text when not connected to WiFi [CHAR LIMIT=NONE] --> - <string name="quick_settings_cast_no_wifi">Wi\u2011Fi not connected</string> + <!-- QuickSettings: Cast unavailable, text when not connected to WiFi or ethernet[CHAR LIMIT=NONE] --> + <string name="quick_settings_cast_no_network">No Wi\u2011Fi or Ethernet connection</string> <!-- QuickSettings: Brightness dialog title [CHAR LIMIT=NONE] --> <string name="quick_settings_brightness_dialog_title">Brightness</string> <!-- QuickSettings: Label for the toggle that controls whether display inversion is enabled. [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/res/xml/self_certified_network_capabilities_both.xml b/packages/SystemUI/res/xml/self_certified_network_capabilities_both.xml new file mode 100644 index 0000000000000000000000000000000000000000..4b430e14643920c3f6ab39d9c4f278518273cde3 --- /dev/null +++ b/packages/SystemUI/res/xml/self_certified_network_capabilities_both.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + ~ + --> +<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android"> + <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/> +</network-capabilities-declaration> diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt index 3360c967a18298590eb69267e8b1e4b2b7060a8e..aef83710c17b016edf7ed58c8cb7215c6d37835a 100644 --- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt +++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt @@ -24,7 +24,7 @@ object FlagsFactory { val knownFlags: Map<String, Flag<*>> get() { // We need to access Flags in order to initialize our map. - assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" } + assert(flagMap.contains(Flags.NULL_FLAG.name)) { "Where is the null flag?" } return flagMap } diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt index 75465c27724dcdea0b102bc4e3a1dff762b47729..f4b429659d8a2dc649b5c0646949338d52583407 100644 --- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt +++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt @@ -24,7 +24,7 @@ object FlagsFactory { val knownFlags: Map<String, Flag<*>> get() { // We need to access Flags in order to initialize our map. - assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" } + assert(flagMap.contains(Flags.NULL_FLAG.name)) { "Where is the null flag?" } return flagMap } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java index 8717a532b43dd42f2d34790d3caf12d6e35b5381..d2d05173564394009e2c935126ae10f316294304 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -31,6 +31,7 @@ import android.telephony.PinResult; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.Log; import android.view.View; import android.view.WindowManager; @@ -39,9 +40,9 @@ import android.widget.ImageView; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.res.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.res.R; public class KeyguardSimPinViewController extends KeyguardPinBasedInputViewController<KeyguardSimPinView> { @@ -324,7 +325,11 @@ public class KeyguardSimPinViewController } else { SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId); CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash - msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName); + if (!TextUtils.isEmpty(displayName)) { + msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName); + } else { + msg = rez.getString(R.string.kg_sim_pin_instructions); + } if (info != null) { color = info.getIconTint(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java index 248b7afd753558c5c2c8a952c1c7efdf2e132241..b52a36b8199e1d4d5d8d5feda0134123832a909b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -29,6 +29,7 @@ import android.telephony.PinResult; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.Log; import android.view.WindowManager; import android.widget.ImageView; @@ -36,9 +37,9 @@ import android.widget.ImageView; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.res.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.res.R; public class KeyguardSimPukViewController extends KeyguardPinBasedInputViewController<KeyguardSimPukView> { @@ -206,7 +207,11 @@ public class KeyguardSimPukViewController } else { SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId); CharSequence displayName = info != null ? info.getDisplayName() : ""; - msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName); + if (!TextUtils.isEmpty(displayName)) { + msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName); + } else { + msg = rez.getString(R.string.kg_puk_enter_puk_hint); + } if (info != null) { color = info.getIconTint(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt index a590dccd53180831fe47c3d15804baae7d85f1dc..b9b2fd8875d9f3051efefe96f42c465d4e89e6cc 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt @@ -23,8 +23,6 @@ import com.android.systemui.biometrics.domain.interactor.LogContextInteractor import com.android.systemui.biometrics.domain.interactor.LogContextInteractorImpl import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl -import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor -import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl import com.android.systemui.dagger.SysUISingleton import dagger.Binds import dagger.Module @@ -49,10 +47,4 @@ interface BiometricsDomainLayerModule { @Binds @SysUISingleton fun bindsLogContextInteractor(impl: LogContextInteractorImpl): LogContextInteractor - - @Binds - @SysUISingleton - fun providesSideFpsOverlayInteractor( - impl: SideFpsOverlayInteractorImpl - ): SideFpsOverlayInteractor } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt index f36a3ec89e3f3ea07dd5acc05a1f123c3889d416..a317a068405514844fa0162e891c627e4b2d484a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt @@ -54,6 +54,9 @@ interface DisplayStateInteractor { /** Current rotation of the display */ val currentRotation: StateFlow<DisplayRotation> + /** Display change event indicating a change to the given displayId has occurred. */ + val displayChanges: Flow<Int> + /** Called on configuration changes, used to keep the display state in sync */ fun onConfigurationChanged(newConfig: Configuration) } @@ -74,6 +77,8 @@ constructor( screenSizeFoldProvider = foldProvider } + override val displayChanges = displayRepository.displayChangeEvent + override val isFolded: Flow<Boolean> = conflatedCallbackFlow { val sendFoldStateUpdate = { state: Boolean -> diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt deleted file mode 100644 index 75ae061f8ccec1ab76f3167f21d3f9358eeb6887..0000000000000000000000000000000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics.domain.interactor - -import android.hardware.biometrics.SensorLocationInternal -import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository -import com.android.systemui.dagger.SysUISingleton -import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.combine - -/** Business logic for SideFps overlay offsets. */ -interface SideFpsOverlayInteractor { - - /** The displayId of the current display. */ - val displayId: Flow<String> - - /** Overlay offsets corresponding to given displayId. */ - val overlayOffsets: Flow<SensorLocationInternal> - - /** Called on display changes, used to keep the display state in sync */ - fun onDisplayChanged(displayId: String) -} - -@SysUISingleton -class SideFpsOverlayInteractorImpl -@Inject -constructor(fingerprintPropertyRepository: FingerprintPropertyRepository) : - SideFpsOverlayInteractor { - - private val _displayId: MutableStateFlow<String> = MutableStateFlow("") - override val displayId: Flow<String> = _displayId.asStateFlow() - - override val overlayOffsets: Flow<SensorLocationInternal> = - combine(displayId, fingerprintPropertyRepository.sensorLocations) { displayId, offsets -> - offsets[displayId] ?: SensorLocationInternal.DEFAULT - } - - override fun onDisplayChanged(displayId: String) { - _displayId.value = displayId - } - - companion object { - private const val TAG = "SideFpsOverlayInteractorImpl" - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt new file mode 100644 index 0000000000000000000000000000000000000000..f85203ea20767cbd3fc58b3c69b21111c7134101 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.domain.interactor + +import android.content.Context +import android.hardware.biometrics.SensorLocationInternal +import android.view.WindowManager +import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository +import com.android.systemui.biometrics.domain.model.SideFpsSensorLocation +import com.android.systemui.biometrics.shared.model.DisplayRotation +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.isDefaultOrientation +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags +import com.android.systemui.res.R +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map + +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class SideFpsSensorInteractor +@Inject +constructor( + private val context: Context, + fingerprintPropertyRepository: FingerprintPropertyRepository, + windowManager: WindowManager, + displayStateInteractor: DisplayStateInteractor, + featureFlags: FeatureFlagsClassic, +) { + + private val sensorForCurrentDisplay = + combine( + displayStateInteractor.displayChanges, + fingerprintPropertyRepository.sensorLocations, + ::Pair + ) + .map { (_, locations) -> locations[context.display?.uniqueId] } + .filterNotNull() + + val isAvailable: Flow<Boolean> = + fingerprintPropertyRepository.sensorType.map { it == FingerprintSensorType.POWER_BUTTON } + + val authenticationDuration: Flow<Long> = + flowOf(context.resources?.getInteger(R.integer.config_restToUnlockDuration)?.toLong() ?: 0L) + + val isProlongedTouchRequiredForAuthentication: Flow<Boolean> = + isAvailable.flatMapLatest { sfpsAvailable -> + if (sfpsAvailable) { + // todo (b/305236201) also add the settings check here. + flowOf(featureFlags.isEnabled(Flags.REST_TO_UNLOCK)) + } else { + flowOf(false) + } + } + + val sensorLocation: Flow<SideFpsSensorLocation> = + combine(displayStateInteractor.currentRotation, sensorForCurrentDisplay, ::Pair).map { + (rotation, sensorLocation: SensorLocationInternal) -> + val isSensorVerticalInDefaultOrientation = sensorLocation.sensorLocationY != 0 + // device dimensions in the current rotation + val size = windowManager.maximumWindowMetrics.bounds + val isDefaultOrientation = rotation.isDefaultOrientation() + // Width and height are flipped is device is not in rotation_0 or rotation_180 + // Flipping it to the width and height of the device in default orientation. + val displayWidth = if (isDefaultOrientation) size.width() else size.height() + val displayHeight = if (isDefaultOrientation) size.height() else size.width() + val sensorWidth = context.resources?.getInteger(R.integer.config_sfpsSensorWidth) ?: 0 + + val (sensorLeft, sensorTop) = + if (isSensorVerticalInDefaultOrientation) { + when (rotation) { + DisplayRotation.ROTATION_0 -> { + Pair(displayWidth, sensorLocation.sensorLocationY) + } + DisplayRotation.ROTATION_90 -> { + Pair(sensorLocation.sensorLocationY, 0) + } + DisplayRotation.ROTATION_180 -> { + Pair(0, displayHeight - sensorLocation.sensorLocationY - sensorWidth) + } + DisplayRotation.ROTATION_270 -> { + Pair( + displayHeight - sensorLocation.sensorLocationY - sensorWidth, + displayWidth + ) + } + } + } else { + when (rotation) { + DisplayRotation.ROTATION_0 -> { + Pair(sensorLocation.sensorLocationX, 0) + } + DisplayRotation.ROTATION_90 -> { + Pair(0, displayWidth - sensorLocation.sensorLocationX - sensorWidth) + } + DisplayRotation.ROTATION_180 -> { + Pair( + displayWidth - sensorLocation.sensorLocationX - sensorWidth, + displayHeight + ) + } + DisplayRotation.ROTATION_270 -> { + Pair(displayHeight, sensorLocation.sensorLocationX) + } + } + } + + SideFpsSensorLocation( + left = sensorLeft, + top = sensorTop, + width = sensorWidth, + isSensorVerticalInDefaultOrientation = isSensorVerticalInDefaultOrientation + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt new file mode 100644 index 0000000000000000000000000000000000000000..35f8e3bb461ff1152a33f360f2cc42beea457541 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.domain.model + +data class SideFpsSensorLocation( + /** Pixel offset from the left of the screen */ + val left: Int, + /** Pixel offset from the top of the screen */ + val top: Int, + /** Width in pixels of the SFPS sensor */ + val width: Int, + /** + * Whether the sensor is vertical when the device is in its default orientation (Rotation_0 or + * Rotation_180) + */ + val isSensorVerticalInDefaultOrientation: Boolean +) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt index 10a3e915fe806128d79d330fa05070dc8ec27740..336404c04dbfceda3c03e73080f5c9eb728f730a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt @@ -10,6 +10,9 @@ enum class DisplayRotation { ROTATION_270, } +fun DisplayRotation.isDefaultOrientation() = + this == DisplayRotation.ROTATION_0 || this == DisplayRotation.ROTATION_180 + /** Converts [Surface.Rotation] to corresponding [DisplayRotation] */ fun Int.toDisplayRotation(): DisplayRotation = when (this) { @@ -19,3 +22,12 @@ fun Int.toDisplayRotation(): DisplayRotation = Surface.ROTATION_270 -> DisplayRotation.ROTATION_270 else -> throw IllegalArgumentException("Invalid DisplayRotation value: $this") } + +/** Converts [DisplayRotation] to corresponding [Surface.Rotation] */ +fun DisplayRotation.toRotation(): Int = + when (this) { + DisplayRotation.ROTATION_0 -> Surface.ROTATION_0 + DisplayRotation.ROTATION_90 -> Surface.ROTATION_90 + DisplayRotation.ROTATION_180 -> Surface.ROTATION_180 + DisplayRotation.ROTATION_270 -> Surface.ROTATION_270 + } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index 04a9cae313826e78a28ece4a51cbd508a309a389..d57f31f91df1c21580010786d55c6d50f555987e 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -67,6 +67,7 @@ import android.hardware.input.InputManager; import android.media.AudioManager; import android.media.IAudioService; import android.media.MediaRouter2Manager; +import android.media.projection.IMediaProjectionManager; import android.media.projection.MediaProjectionManager; import android.media.session.MediaSessionManager; import android.net.ConnectivityManager; @@ -413,6 +414,13 @@ public class FrameworkServicesModule { return context.getSystemService(MediaProjectionManager.class); } + @Provides + @Singleton + static IMediaProjectionManager provideIMediaProjectionManager() { + return IMediaProjectionManager.Stub.asInterface( + ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE)); + } + @Provides static MediaRouter2Manager provideMediaRouter2Manager(Context context) { return MediaRouter2Manager.getInstance(context); diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt index 7510cf6ce04c964e238ffbaf2089642add6322ef..d19efbdd80269d579cdac2a43a0e576fb4bf4350 100644 --- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt @@ -15,14 +15,12 @@ */ package com.android.systemui.display.ui.view -import android.app.Dialog import android.content.Context import android.os.Bundle -import android.view.Gravity import android.view.View -import android.view.WindowManager import android.widget.TextView import com.android.systemui.res.R +import com.android.systemui.statusbar.phone.SystemUIBottomSheetDialog /** * Dialog used to decide what to do with a connected display. @@ -35,7 +33,7 @@ class MirroringConfirmationDialog( private val onStartMirroringClickListener: View.OnClickListener, private val onCancelMirroring: View.OnClickListener, theme: Int = R.style.Theme_SystemUI_Dialog, -) : Dialog(context, theme) { +) : SystemUIBottomSheetDialog(context, theme) { private lateinit var mirrorButton: TextView private lateinit var dismissButton: TextView @@ -43,13 +41,8 @@ class MirroringConfirmationDialog( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - window?.apply { - setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL) - addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS) - setGravity(Gravity.BOTTOM) - } setContentView(R.layout.connected_display_dialog) - setCanceledOnTouchOutside(true) + mirrorButton = requireViewById<TextView>(R.id.enable_display).apply { setOnClickListener(onStartMirroringClickListener) diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java index 694695017efdea76281dbd8c03148ea97d1f6076..1c2ff4b1b42c9ca0ede5255c58617b3505189df6 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java @@ -36,6 +36,7 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.systemui.FeatureFlags; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.util.settings.GlobalSettings; @@ -80,6 +81,7 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { private final Map<String, Boolean> mBooleanFlagCache = new ConcurrentHashMap<>(); private final Map<String, String> mStringFlagCache = new ConcurrentHashMap<>(); private final Map<String, Integer> mIntFlagCache = new ConcurrentHashMap<>(); + private final FeatureFlags mGantryFlags; private final Restarter mRestarter; private final ServerFlagReader.ChangeListener mOnPropertiesChanged = @@ -124,6 +126,7 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { @Main Resources resources, ServerFlagReader serverFlagReader, @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags, + FeatureFlags gantryFlags, Restarter restarter) { mFlagManager = flagManager; mContext = context; @@ -132,6 +135,7 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { mSystemProperties = systemProperties; mServerFlagReader = serverFlagReader; mAllFlags = allFlags; + mGantryFlags = gantryFlags; mRestarter = restarter; } @@ -259,9 +263,8 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { if (!hasServerOverride && !defaultValue && result == null - && !flag.getName().equals(Flags.TEAMFOOD.getName()) && flag.getTeamfood()) { - return isEnabled(Flags.TEAMFOOD); + return mGantryFlags.sysuiTeamfood(); } return result == null ? mServerFlagReader.readServerOverride( @@ -534,7 +537,7 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { pw.println("can override: true"); - + pw.println("teamfood: " + mGantryFlags.sysuiTeamfood()); pw.println("booleans: " + mBooleanFlagCache.size()); // Sort our flags for dumping TreeMap<String, Boolean> dumpBooleanMap = new TreeMap<>(mBooleanFlagCache); diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 11ac39ff867b99ea07a6e2d46a308c5757aa8e1b..b7c3662baa33b2e50087fca2177d1f90d86ce54a 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -36,7 +36,10 @@ import com.android.systemui.flags.FlagsFactory.unreleasedFlag * See [FeatureFlagsClassicDebug] for instructions on flipping the flags via adb. */ object Flags { - @JvmField val TEAMFOOD = unreleasedFlag("teamfood") + // IGNORE ME! + // Because flags are static, we need an ever-present flag to reference in some of the internal + // code that ensure that other flags are referenced and available. + @JvmField val NULL_FLAG = unreleasedFlag("null_flag") // 100 - notification // TODO(b/297792660): Tracking Bug @@ -401,6 +404,9 @@ object Flags { @JvmField val SIGNAL_CALLBACK_DEPRECATION = unreleasedFlag("signal_callback_deprecation", teamfood = true) + // TODO(b/301610137): Tracking bug + @JvmField val NEW_NETWORK_SLICE_UI = unreleasedFlag("new_network_slice_ui", teamfood = true) + // TODO(b/265892345): Tracking Bug val PLUG_IN_STATUS_BAR_CHIP = releasedFlag("plug_in_status_bar_chip") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index fde92b85cac37eb51fc5eb9016075216e828be1a..0bac40bcbcc13bbe28ddda92a5aef7ecd6bc13ff 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -451,7 +451,8 @@ class KeyguardUnlockAnimationController @Inject constructor( if (!keyguardStateController.isKeyguardGoingAway && willUnlockWithInWindowLauncherAnimations) { try { - launcherUnlockController?.setUnlockAmount(1f, true /* forceIfAnimating */) + launcherUnlockController?.setUnlockAmount(1f, + biometricUnlockControllerLazy.get().isWakeAndUnlock /* forceIfAnimating */) } catch (e: DeadObjectException) { Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null in " + "onKeyguardGoingAwayChanged(). Catching exception as this should mean " + diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt new file mode 100644 index 0000000000000000000000000000000000000000..8634b0911391504687c3c94502453cb4b3938dcb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt @@ -0,0 +1,83 @@ +/* + * 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.mediaprojection + +import android.media.projection.IMediaProjectionManager +import android.os.Process +import android.os.RemoteException +import android.util.Log +import com.android.internal.util.FrameworkStatsLog +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** + * Helper class for requesting that the server emit logs describing the MediaProjection setup + * experience. + */ +@SysUISingleton +class MediaProjectionMetricsLogger +@Inject +constructor(private val service: IMediaProjectionManager) { + /** + * Request to log that the permission was requested. + * + * @param sessionCreationSource The entry point requesting permission to capture. + */ + fun notifyPermissionProgress(state: Int, sessionCreationSource: Int) { + // TODO check that state & SessionCreationSource matches expected values + notifyToServer(state, sessionCreationSource) + } + + /** + * Request to log that the permission request moved to the given state. + * + * Should not be used for the initialization state, since that + */ + fun notifyPermissionProgress(state: Int) { + // TODO validate state is valid + notifyToServer( + state, + FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN) + } + + /** + * Notifies system server that we are handling a particular state during the consent flow. + * + * Only used for emitting atoms. + * + * @param state The state that SystemUI is handling during the consent flow. Must be a valid + * state defined in the MediaProjectionState enum. + * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED. + * Indicates the entry point for requesting the permission. Must be a valid state defined in + * the SessionCreationSource enum. + */ + private fun notifyToServer(state: Int, sessionCreationSource: Int) { + Log.v(TAG, "FOO notifyToServer of state $state and source $sessionCreationSource") + try { + service.notifyPermissionRequestStateChange( + Process.myUid(), state, sessionCreationSource) + } catch (e: RemoteException) { + Log.e( + TAG, + "Error notifying server of permission flow state $state from source $sessionCreationSource", + e) + } + } + + companion object { + const val TAG = "MediaProjectionMetricsLogger" + } +} 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 2f10ad3e64862d44e3fa1fcbbcc6b9d36f0a5f25..b9bafd4926fa773514be32d22c33062a32033a6e 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt @@ -88,16 +88,16 @@ class MediaProjectionPermissionDialog( null } 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 37e8d9f26ee34aff23148f56aa6688a4961b71ba..9bd57832c4dff8884583098e6ac79c6d1b731126 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/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index 5c6e9028545a786c5ae446e283fc0de9c0070be9..a698208688381476bbb19b6f6dfff235eaa109fb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -38,7 +38,6 @@ import com.android.internal.app.MediaRouteDialogPresenter; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.res.R; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.DialogCuj; import com.android.systemui.animation.DialogLaunchAnimator; @@ -53,12 +52,13 @@ import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.res.R; import com.android.systemui.statusbar.connectivity.NetworkController; import com.android.systemui.statusbar.connectivity.SignalCallback; import com.android.systemui.statusbar.connectivity.WifiIndicators; import com.android.systemui.statusbar.phone.SystemUIDialog; -import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor; -import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel; +import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel; +import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.CastController.CastDevice; import com.android.systemui.statusbar.policy.HotspotController; @@ -85,10 +85,9 @@ public class CastTile extends QSTileImpl<BooleanState> { private final NetworkController mNetworkController; private final DialogLaunchAnimator mDialogLaunchAnimator; private final Callback mCallback = new Callback(); - private final WifiInteractor mWifiInteractor; private final TileJavaAdapter mJavaAdapter; private final FeatureFlags mFeatureFlags; - private boolean mWifiConnected; + private boolean mCastTransportAllowed; private boolean mHotspotConnected; @Inject @@ -107,7 +106,7 @@ public class CastTile extends QSTileImpl<BooleanState> { NetworkController networkController, HotspotController hotspotController, DialogLaunchAnimator dialogLaunchAnimator, - WifiInteractor wifiInteractor, + ConnectivityRepository connectivityRepository, TileJavaAdapter javaAdapter, FeatureFlags featureFlags ) { @@ -117,7 +116,6 @@ public class CastTile extends QSTileImpl<BooleanState> { mKeyguard = keyguardStateController; mNetworkController = networkController; mDialogLaunchAnimator = dialogLaunchAnimator; - mWifiInteractor = wifiInteractor; mJavaAdapter = javaAdapter; mFeatureFlags = featureFlags; mController.observe(this, mCallback); @@ -125,7 +123,11 @@ public class CastTile extends QSTileImpl<BooleanState> { if (!mFeatureFlags.isEnabled(SIGNAL_CALLBACK_DEPRECATION)) { mNetworkController.observe(this, mSignalCallback); } else { - mJavaAdapter.bind(this, mWifiInteractor.getWifiNetwork(), mNetworkModelConsumer); + mJavaAdapter.bind( + this, + connectivityRepository.getDefaultConnections(), + mNetworkModelConsumer + ); } hotspotController.observe(this, mHotspotCallback); } @@ -282,7 +284,7 @@ public class CastTile extends QSTileImpl<BooleanState> { } state.icon = ResourceIcon.get(state.value ? R.drawable.ic_cast_connected : R.drawable.ic_cast); - if (canCastToWifi() || state.value) { + if (canCastToNetwork() || state.value) { state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; if (!state.value) { state.secondaryLabel = ""; @@ -291,7 +293,7 @@ public class CastTile extends QSTileImpl<BooleanState> { state.forceExpandIcon = willPopDialog(); } else { state.state = Tile.STATE_UNAVAILABLE; - String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi); + String noWifi = mContext.getString(R.string.quick_settings_cast_no_network); state.secondaryLabel = noWifi; state.forceExpandIcon = false; } @@ -308,13 +310,13 @@ public class CastTile extends QSTileImpl<BooleanState> { : mContext.getString(R.string.quick_settings_cast_device_default_name); } - private boolean canCastToWifi() { - return mWifiConnected || mHotspotConnected; + private boolean canCastToNetwork() { + return mCastTransportAllowed || mHotspotConnected; } - private void setWifiConnected(boolean connected) { - if (connected != mWifiConnected) { - mWifiConnected = connected; + private void setCastTransportAllowed(boolean connected) { + if (connected != mCastTransportAllowed) { + mCastTransportAllowed = connected; // Hotspot is not connected, so changes here should update if (!mHotspotConnected) { refreshState(); @@ -326,14 +328,17 @@ public class CastTile extends QSTileImpl<BooleanState> { if (connected != mHotspotConnected) { mHotspotConnected = connected; // Wifi is not connected, so changes here should update - if (!mWifiConnected) { + if (!mCastTransportAllowed) { refreshState(); } } } - private final Consumer<WifiNetworkModel> mNetworkModelConsumer = (model) -> { - setWifiConnected(model instanceof WifiNetworkModel.Active); + private final Consumer<DefaultConnectionModel> mNetworkModelConsumer = (model) -> { + boolean isWifiDefault = model.getWifi().isDefault(); + boolean isEthernetDefault = model.getEthernet().isDefault(); + boolean hasCellularTransport = model.getMobile().isDefault(); + setCastTransportAllowed((isWifiDefault || isEthernetDefault) && !hasCellularTransport); }; private final SignalCallback mSignalCallback = new SignalCallback() { @@ -342,7 +347,7 @@ public class CastTile extends QSTileImpl<BooleanState> { // statusIcon.visible has the connected status information boolean enabledAndConnected = indicators.enabled && (indicators.qsIcon != null && indicators.qsIcon.visible); - setWifiConnected(enabledAndConnected); + setCastTransportAllowed(enabledAndConnected); } }; diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt index a1d5d98ba9e92b78b893537b82deb20d3d80faa9..ade93b1a913edc84ec4d78c36efcfc56134d7c1f 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt @@ -185,15 +185,15 @@ 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/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 4b3bd0b44bc9e60d0c84638dd850a615c1e87114..127a57e26a30ae1e684e0f322bb1dbf1e3de465f 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -303,6 +303,13 @@ public class ScreenshotController { private String mPackageName = ""; private BroadcastReceiver mCopyBroadcastReceiver; + // When false, the screenshot is taken without showing the ui. Note that this only applies to + // external displays, as on the default one the UI should **always** be shown. + // This is needed in case of screenshot during display mirroring, as adding another window to + // the external display makes mirroring stop. + // When there is a way to distinguish between displays that are mirroring or extending, this + // can be removed and we can directly show the ui only in the extended case. + private final Boolean mShowUIOnExternalDisplay; /** Tracks config changes that require re-creating UI */ private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( ActivityInfo.CONFIG_ORIENTATION @@ -335,7 +342,8 @@ public class ScreenshotController { AssistContentRequester assistContentRequester, MessageContainerController messageContainerController, Provider<ScreenshotSoundController> screenshotSoundController, - @Assisted int displayId + @Assisted int displayId, + @Assisted boolean showUIOnExternalDisplay ) { mScreenshotSmartActions = screenshotSmartActions; mNotificationsController = screenshotNotificationsController; @@ -401,6 +409,7 @@ public class ScreenshotController { mContext.registerReceiver(mCopyBroadcastReceiver, new IntentFilter( ClipboardOverlayController.COPY_OVERLAY_ACTION), ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED); + mShowUIOnExternalDisplay = showUIOnExternalDisplay; } void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher, @@ -448,6 +457,23 @@ public class ScreenshotController { prepareViewForNewScreenshot(screenshot, oldPackageName); + if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && screenshot.getTaskId() >= 0) { + mAssistContentRequester.requestAssistContent(screenshot.getTaskId(), + new AssistContentRequester.Callback() { + @Override + public void onAssistContentAvailable(AssistContent assistContent) { + screenshot.setContextUrl(assistContent.getWebUri()); + } + }); + } + + if (!shouldShowUi()) { + saveScreenshotInWorkerThread( + screenshot.getUserHandle(), finisher, this::logSuccessOnActionsReady, + (ignored) -> {}); + return; + } + saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher, this::showUiOnActionsReady, this::showUiOnQuickShareActionReady); @@ -482,22 +508,16 @@ public class ScreenshotController { screenshot.getUserHandle())); mScreenshotView.setScreenshot(screenshot); - if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && screenshot.getTaskId() >= 0) { - mAssistContentRequester.requestAssistContent(screenshot.getTaskId(), - new AssistContentRequester.Callback() { - @Override - public void onAssistContentAvailable(AssistContent assistContent) { - screenshot.setContextUrl(assistContent.getWebUri()); - } - }); - } - // ignore system bar insets for the purpose of window layout mWindow.getDecorView().setOnApplyWindowInsetsListener( (v, insets) -> WindowInsets.CONSUMED); mScreenshotHandler.cancelTimeout(); // restarted after animation } + private boolean shouldShowUi() { + return mDisplayId == Display.DEFAULT_DISPLAY || mShowUIOnExternalDisplay; + } + void prepareViewForNewScreenshot(ScreenshotData screenshot, String oldPackageName) { withWindowAttached(() -> { if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) { @@ -1199,7 +1219,13 @@ public class ScreenshotController { /** Injectable factory to create screenshot controller instances for a specific display. */ @AssistedFactory public interface Factory { - /** Creates an instance of the controller for that specific displayId. */ - ScreenshotController create(int displayId); + /** + * Creates an instance of the controller for that specific displayId. + * + * @param displayId: display to capture + * @param showUIOnExternalDisplay: Whether the UI should be shown if this is an external + * display. + */ + ScreenshotController create(int displayId, boolean showUIOnExternalDisplay); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt index 03c3f7a8ddf37cbfca9801ec3588cab0ee0181ec..abe40ff11ead8c7c1d7a3d853335fdf88738f5c9 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt @@ -135,7 +135,9 @@ constructor( } private fun getScreenshotController(id: Int): ScreenshotController { - return screenshotControllers.computeIfAbsent(id) { screenshotControllerFactory.create(id) } + return screenshotControllers.computeIfAbsent(id) { + screenshotControllerFactory.create(id, /* showUIOnExternalDisplay= */ false) + } } /** For java compatibility only. see [executeScreenshots] */ diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 0be2265bf8fa1047bb551fa26c3fac853a607497..75d52cbe2e36c3dfc566038c68d363bd2808a5c2 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -132,7 +132,8 @@ public class TakeScreenshotService extends Service { if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) { mScreenshot = null; } else { - mScreenshot = screenshotControllerFactory.create(Display.DEFAULT_DISPLAY); + mScreenshot = screenshotControllerFactory.create( + Display.DEFAULT_DISPLAY, /* showUIOnExternalDisplay= */ false); } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index ba0cf08150f6d11dafaaa7b59f13c77d342b9660..51972c2d025c280af7012a94f0e27ee0d2ceba93 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -414,9 +414,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private int mDisplayLeftInset = 0; // in pixels @VisibleForTesting - KeyguardClockPositionAlgorithm - mClockPositionAlgorithm = - new KeyguardClockPositionAlgorithm(); + KeyguardClockPositionAlgorithm mClockPositionAlgorithm; private final KeyguardClockPositionAlgorithm.Result mClockPositionResult = new KeyguardClockPositionAlgorithm.Result(); @@ -779,7 +777,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump KeyguardViewConfigurator keyguardViewConfigurator, KeyguardFaceAuthInteractor keyguardFaceAuthInteractor, SplitShadeStateController splitShadeStateController, - PowerInteractor powerInteractor) { + PowerInteractor powerInteractor, + KeyguardClockPositionAlgorithm keyguardClockPositionAlgorithm) { keyguardStateController.addCallback(new KeyguardStateController.Callback() { @Override public void onKeyguardFadingAwayChanged() { @@ -807,6 +806,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardInteractor = keyguardInteractor; mPowerInteractor = powerInteractor; mKeyguardViewConfigurator = keyguardViewConfigurator; + mClockPositionAlgorithm = keyguardClockPositionAlgorithm; mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt index 0554c5855d61d469c8c0657f136012cf8e77f9aa..1a28ddc9719bcc201e60bb7e8116660cc3529c48 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/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt index 25bd8e7446bce7f3dac0699c2527c0f88bc7183a..cb95b25ece80c10a4504447ea7e19a5ac7d2f2d2 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt @@ -38,7 +38,6 @@ import androidx.core.view.doOnLayout import com.android.app.animation.Interpolators import com.android.settingslib.Utils import com.android.systemui.Dumpable -import com.android.systemui.res.R import com.android.systemui.animation.ShadeInterpolation import com.android.systemui.battery.BatteryMeterView import com.android.systemui.battery.BatteryMeterViewController @@ -49,6 +48,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.ChipVisibilityListener import com.android.systemui.qs.HeaderPrivacyIconsController +import com.android.systemui.res.R import com.android.systemui.shade.ShadeHeaderController.Companion.HEADER_TRANSITION_ID import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID @@ -304,7 +304,8 @@ constructor( iconManager = tintedIconManagerFactory.create(iconContainer, StatusBarLocation.QS) iconManager.setTint( - Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary) + Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary), + Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimaryInverse), ) carrierIconSlots = 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 e487a6fb961794c5c9a027785fff6291393b8712..99370e963c9f8feb819f743f5f42ae840fb27024 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 2147510bbde54677cffabab3f3e73f2233c18ffa..f32f1c2dcd2510baec9cd6396520ab68c550957c 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() { @@ -490,20 +724,6 @@ public class NotificationLockscreenUserManagerImpl implements return false; } - /** - * 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. */ @@ -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 37a4ef168423bf17417edb493e66ccadd541803c..ef45d2b75ba0b1140e48a2581dd8011903b355f1 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/StatusIconDisplayable.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java index 1196211bd6710555d546dd122eefebde81b3b6a1..595ab701a9f2f0cae2bacf1c388ce64e55183b57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java @@ -21,6 +21,21 @@ import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; public interface StatusIconDisplayable extends DarkReceiver { String getSlot(); void setStaticDrawableColor(int color); + + /** + * For a layer drawable, or one that has a background, {@code tintColor} should be used as the + * background tint for the container, while {@code contrastColor} can be used as the foreground + * drawable's tint so that it is visible on the background. Essentially, tintColor should apply + * to the portion of the icon that borders the underlying window content (status bar's + * background), and the contrastColor only need be used to distinguish from the tintColor. + * + * Defaults to calling {@link #setStaticDrawableColor(int)} with only the tint color, so modern + * callers can just call this method and still get the default behavior. + */ + default void setStaticDrawableColor(int tintColor, int contrastColor) { + setStaticDrawableColor(tintColor); + } + void setDecorColor(int color); /** Sets the visible state that this displayable should be. */ 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 b2c32cd42d1d9bf1d457915cd0e6780f79b4792a..db7f46eb28f2f3ad72738853b188079727e45f3e 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 6ec9dbe003a2fa638e8ef3b048f12e7bb280b45e..b0155f13dbec5203cf32f6dd5696f867975cfe7f 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 3819843aa7b27a5f660a921ff54c7db02bc52063..778a0a90cd85dd7efecb1f86dbd7d9c973a651f2 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 @@ -174,6 +174,11 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter mSuppressors.add(suppressor); } + @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 ebdeded6e329c06cf5794e3c42b376e1b921ab6b..d7f0baf4f00278543f0b524b177d7bca0b3e32dd 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 454ba02b2d7363b4219c8e2638d80f2c52c19811..920bbe96b33b2d10443093a9301a73cae037e633 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 @@ -59,6 +59,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/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java index 1576aa2a4a0c1e44419bedabff237aafd4d1a9c6..6f992ac815cb07adc999716f9a7af5d039824884 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java @@ -47,6 +47,11 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher, private final ArrayMap<Object, DarkReceiver> mReceivers = new ArrayMap<>(); private int mIconTint = DEFAULT_ICON_TINT; + private int mContrastTint = DEFAULT_INVERSE_ICON_TINT; + + private int mDarkModeContrastColor = DEFAULT_ICON_TINT; + private int mLightModeContrastColor = DEFAULT_INVERSE_ICON_TINT; + private float mDarkIntensity; private int mDarkModeIconColorSingleTone; private int mLightModeIconColorSingleTone; @@ -83,6 +88,7 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher, public void addDarkReceiver(DarkReceiver receiver) { mReceivers.put(receiver, receiver); receiver.onDarkChanged(mTintAreas, mDarkIntensity, mIconTint); + receiver.onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint); } public void addDarkReceiver(ImageView imageView) { @@ -90,6 +96,7 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher, ColorStateList.valueOf(getTint(mTintAreas, imageView, mIconTint))); mReceivers.put(imageView, receiver); receiver.onDarkChanged(mTintAreas, mDarkIntensity, mIconTint); + receiver.onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint); } public void removeDarkReceiver(DarkReceiver object) { @@ -102,6 +109,7 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher, public void applyDark(DarkReceiver object) { mReceivers.get(object).onDarkChanged(mTintAreas, mDarkIntensity, mIconTint); + mReceivers.get(object).onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint); } /** @@ -125,8 +133,13 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher, @Override public void applyDarkIntensity(float darkIntensity) { mDarkIntensity = darkIntensity; - mIconTint = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, + ArgbEvaluator evaluator = ArgbEvaluator.getInstance(); + + mIconTint = (int) evaluator.evaluate(darkIntensity, mLightModeIconColorSingleTone, mDarkModeIconColorSingleTone); + mContrastTint = (int) evaluator + .evaluate(darkIntensity, mLightModeContrastColor, mDarkModeContrastColor); + applyIconTint(); } @@ -139,6 +152,7 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher, mDarkChangeFlow.setValue(new DarkChange(mTintAreas, mDarkIntensity, mIconTint)); for (int i = 0; i < mReceivers.size(); i++) { mReceivers.valueAt(i).onDarkChanged(mTintAreas, mDarkIntensity, mIconTint); + mReceivers.valueAt(i).onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint); } } @@ -146,6 +160,16 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher, public void dump(PrintWriter pw, String[] args) { pw.println("DarkIconDispatcher: "); pw.println(" mIconTint: 0x" + Integer.toHexString(mIconTint)); + pw.println(" mContrastTint: 0x" + Integer.toHexString(mContrastTint)); + + pw.println(" mDarkModeIconColorSingleTone: 0x" + + Integer.toHexString(mDarkModeIconColorSingleTone)); + pw.println(" mLightModeIconColorSingleTone: 0x" + + Integer.toHexString(mLightModeIconColorSingleTone)); + + pw.println(" mDarkModeContrastColor: 0x" + Integer.toHexString(mDarkModeContrastColor)); + pw.println(" mLightModeContrastColor: 0x" + Integer.toHexString(mLightModeContrastColor)); + pw.println(" mDarkIntensity: " + mDarkIntensity + "f"); pw.println(" mTintAreas: " + mTintAreas); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java index de9854afdba1b5618b1e2e12ac8a3d51fb36a6dc..5deb08a75dff95a9f8e3acd8413a3a3652775854 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java @@ -28,10 +28,10 @@ import android.view.ViewGroup; import android.widget.LinearLayout; import com.android.internal.statusbar.StatusBarIcon; -import com.android.systemui.res.R; import com.android.systemui.demomode.DemoMode; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; +import com.android.systemui.res.R; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.StatusIconDisplayable; import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger; @@ -54,6 +54,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da private ModernStatusBarWifiView mModernWifiView; private boolean mDemoMode; private int mColor; + private int mContrastColor; private final MobileIconsViewModel mMobileIconsViewModel; private final StatusBarLocation mLocation; @@ -68,6 +69,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da mStatusIcons = statusIcons; mIconSize = iconSize; mColor = DarkIconDispatcher.DEFAULT_ICON_TINT; + mContrastColor = DarkIconDispatcher.DEFAULT_INVERSE_ICON_TINT; mMobileIconsViewModel = mobileIconsViewModel; mLocation = location; @@ -89,15 +91,17 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da ((ViewGroup) getParent()).removeView(this); } - public void setColor(int color) { + /** Set the tint colors */ + public void setColor(int color, int contrastColor) { mColor = color; + mContrastColor = contrastColor; updateColors(); } private void updateColors() { for (int i = 0; i < getChildCount(); i++) { StatusIconDisplayable child = (StatusIconDisplayable) getChildAt(i); - child.setStaticDrawableColor(mColor); + child.setStaticDrawableColor(mColor, mContrastColor); child.setDecorColor(mColor); } } @@ -223,7 +227,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da StatusBarIconView v = new StatusBarIconView(getContext(), slot, null, false); v.setTag(slot); v.set(icon); - v.setStaticDrawableColor(mColor); + v.setStaticDrawableColor(mColor, mContrastColor); v.setDecorColor(mColor); addView(v, 0, createLayoutParams()); } @@ -269,7 +273,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da } mModernWifiView = view; - mModernWifiView.setStaticDrawableColor(mColor); + mModernWifiView.setStaticDrawableColor(mColor, mContrastColor); addView(view, viewIndex, createLayoutParams()); } @@ -305,14 +309,20 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da } @Override - public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) { - setColor(DarkIconDispatcher.getTint(areas, mStatusIcons, tint)); + public void onDarkChangedWithContrast(ArrayList<Rect> areas, int tint, int contrastTint) { + setColor(tint, contrastTint); if (mModernWifiView != null) { - mModernWifiView.onDarkChanged(areas, darkIntensity, tint); + mModernWifiView.onDarkChangedWithContrast(areas, tint, contrastTint); } + for (ModernStatusBarMobileView view : mModernMobileViews) { - view.onDarkChanged(areas, darkIntensity, tint); + view.onDarkChangedWithContrast(areas, tint, contrastTint); } } + + @Override + public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) { + // not needed + } } 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 6b4382f731eab9cbd18f22a622ff2f51cd114004..407148faeb3016763057fe9c9c41880972a5640a 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/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java index fb5a530e38754431204e89906de2063e8ba198f8..0a03af7d9387b03ad0f67a5d0c71a81b5b807b39 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -26,14 +26,20 @@ import android.util.MathUtils; import com.android.app.animation.Interpolators; import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.keyguard.KeyguardStatusView; +import com.android.systemui.log.LogBuffer; +import com.android.systemui.log.core.Logger; +import com.android.systemui.log.dagger.KeyguardClockLog; import com.android.systemui.res.R; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView; +import javax.inject.Inject; + /** * Utility class to calculate the clock position and top padding of notifications on Keyguard. */ public class KeyguardClockPositionAlgorithm { + private static final String TAG = "KeyguardClockPositionAlgorithm"; /** * Margin between the bottom of the status view and the notification shade. @@ -147,6 +153,13 @@ public class KeyguardClockPositionAlgorithm { */ private boolean mIsClockTopAligned; + private Logger mLogger; + + @Inject + public KeyguardClockPositionAlgorithm(@KeyguardClockLog LogBuffer logBuffer) { + mLogger = new Logger(logBuffer, TAG); + } + /** * Refreshes the dimension values. */ @@ -306,6 +319,20 @@ public class KeyguardClockPositionAlgorithm { + fullyDarkBurnInOffset + shift; mCurrentBurnInOffsetY = MathUtils.lerp(0, fullyDarkBurnInOffset, darkAmount); + final String inputs = "panelExpansion: " + panelExpansion + " darkAmount: " + darkAmount; + final String outputs = "clockY: " + clockY + + " burnInPreventionOffsetY: " + burnInPreventionOffsetY + + " fullyDarkBurnInOffset: " + fullyDarkBurnInOffset + + " shift: " + shift + + " mOverStretchAmount: " + mOverStretchAmount + + " mCurrentBurnInOffsetY: " + mCurrentBurnInOffsetY; + mLogger.i(msg -> { + return msg.getStr1() + " -> " + msg.getStr2(); + }, msg -> { + msg.setStr1(inputs); + msg.setStr2(outputs); + return kotlin.Unit.INSTANCE; + }); return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 58126ae41a1d41af3cf200c6ebbe5ff107e85102..8a64a509a0e940fd03e130ea232c902eca8e34cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -436,10 +436,14 @@ public class KeyguardStatusBarView extends RelativeLayout { private void updateIconsAndTextColors(StatusBarIconController.TintedIconManager iconManager) { @ColorInt int textColor = Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor); + float luminance = Color.luminance(textColor); @ColorInt int iconColor = Utils.getColorStateListDefaultColor(mContext, - Color.luminance(textColor) < 0.5 + luminance < 0.5 ? com.android.settingslib.R.color.dark_mode_icon_color_single_tone : com.android.settingslib.R.color.light_mode_icon_color_single_tone); + @ColorInt int contrastColor = luminance < 0.5 + ? DarkIconDispatcherImpl.DEFAULT_ICON_TINT + : DarkIconDispatcherImpl.DEFAULT_INVERSE_ICON_TINT; float intensity = textColor == Color.WHITE ? 0 : 1; mCarrierLabel.setTextColor(iconColor); @@ -451,7 +455,7 @@ public class KeyguardStatusBarView extends RelativeLayout { } if (iconManager != null) { - iconManager.setTint(iconColor); + iconManager.setTint(iconColor, contrastColor); } mDarkChange.setValue(new DarkChange(mEmptyTintRect, intensity, iconColor)); 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 4d9de09fded41be41e7d3f38d898be0d160264e5..fa6d2797a37e66f9f8d8147c472979878504845d 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/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index ffeb1a8cb8b601daa94f1d7f324ff6a33db5e0e9..9ae41951bb7406bc30e8f981bbe77e3a1680c34d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -31,11 +31,11 @@ import android.widget.LinearLayout.LayoutParams; import androidx.annotation.VisibleForTesting; import com.android.internal.statusbar.StatusBarIcon; -import com.android.systemui.res.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.demomode.DemoModeCommandReceiver; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; +import com.android.systemui.res.R; import com.android.systemui.statusbar.BaseStatusBarFrameLayout; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.StatusIconDisplayable; @@ -248,7 +248,10 @@ public interface StatusBarIconController { * */ class TintedIconManager extends IconManager { + // The main tint, used as the foreground in non layer drawables private int mColor; + // To be used as the main tint in drawables that wish to have a layer + private int mForegroundColor; public TintedIconManager( ViewGroup group, @@ -268,26 +271,41 @@ public interface StatusBarIconController { protected void onIconAdded(int index, String slot, boolean blocked, StatusBarIconHolder holder) { StatusIconDisplayable view = addHolder(index, slot, blocked, holder); - view.setStaticDrawableColor(mColor); + view.setStaticDrawableColor(mColor, mForegroundColor); view.setDecorColor(mColor); } - public void setTint(int color) { - mColor = color; + /** + * Most icons are a single layer, and tintColor will be used as the tint in those cases. + * For icons that have a background, foregroundColor becomes the contrasting tint used + * for the foreground. + * + * @param tintColor the main tint to use for the icons in the group + * @param foregroundColor used as the main tint for layer-ish drawables where tintColor is + * being used as the background + */ + public void setTint(int tintColor, int foregroundColor) { + mColor = tintColor; + mForegroundColor = foregroundColor; + for (int i = 0; i < mGroup.getChildCount(); i++) { View child = mGroup.getChildAt(i); if (child instanceof StatusIconDisplayable) { StatusIconDisplayable icon = (StatusIconDisplayable) child; - icon.setStaticDrawableColor(mColor); + icon.setStaticDrawableColor(mColor, mForegroundColor); icon.setDecorColor(mColor); } } + + if (mDemoStatusIcons != null) { + mDemoStatusIcons.setColor(tintColor, foregroundColor); + } } @Override protected DemoStatusIcons createDemoStatusIcons() { DemoStatusIcons icons = super.createDemoStatusIcons(); - icons.setColor(mColor); + icons.setColor(mColor, mForegroundColor); return icons; } 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 ba73c1098637cfc7fafa200a4ae605e584d0b9f5..6d8ec44ad55ee5dff95169e8de384dac76d4b9da 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/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt new file mode 100644 index 0000000000000000000000000000000000000000..85fd2afed9ec46bdc89fcc73ead6aeb2a849d38e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt @@ -0,0 +1,58 @@ +/* + * 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.phone + +import android.app.Dialog +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.view.Gravity +import android.view.WindowManager +import com.android.systemui.res.R + +/** A dialog shown as a bottom sheet. */ +open class SystemUIBottomSheetDialog( + context: Context, + theme: Int = R.style.Theme_SystemUI_Dialog, +) : Dialog(context, theme) { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + window?.apply { + setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL) + addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS) + + setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + setGravity(Gravity.BOTTOM) + val edgeToEdgeHorizontally = + context.resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog) + if (edgeToEdgeHorizontally) { + decorView.setPadding(0, 0, 0, 0) + setLayout( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.WRAP_CONTENT + ) + + val lp = attributes + lp.fitInsetsSides = 0 + lp.horizontalMargin = 0f + attributes = lp + } + } + setCanceledOnTouchOutside(true) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt index aacdc6323179f86cb4c77a2accb2111953f84247..3522b9a139899373c1f805095e882a691c5e716f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt @@ -190,6 +190,24 @@ constructor( fun logOnSimStateChanged() { buffer.log(TAG, LogLevel.INFO, "onSimStateChanged") } + + fun logPrioritizedNetworkAvailable(netId: Int) { + buffer.log( + TAG, + LogLevel.INFO, + { int1 = netId }, + { "Found prioritized network (nedId=$int1)" }, + ) + } + + fun logPrioritizedNetworkLost(netId: Int) { + buffer.log( + TAG, + LogLevel.INFO, + { int1 = netId }, + { "Lost prioritized network (nedId=$int1)" }, + ) + } } private const val TAG = "MobileInputLog" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt index a89b1b2db6b31bc6576298280a26990edcdea0c7..679426db99c09a66321a38568b5985191532e238 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt @@ -131,6 +131,12 @@ interface MobileConnectionRepository { */ val isAllowedDuringAirplaneMode: StateFlow<Boolean> + /** + * True if this network has NET_CAPABILITIY_PRIORITIZE_LATENCY, and can be considered to be a + * network slice + */ + val hasPrioritizedNetworkCapabilities: StateFlow<Boolean> + companion object { /** The default number of levels to use for [numberOfLevels]. */ const val DEFAULT_NUM_LEVELS = 4 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt index c576b822da1582b1b1613feb619feebe56284c45..caa9d1a8f0c326f7685a6dd076610949c7cc26a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt @@ -191,6 +191,8 @@ class DemoMobileConnectionRepository( override val isAllowedDuringAirplaneMode = MutableStateFlow(false) + override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false) + /** * Process a new demo mobile event. Note that [resolvedNetworkType] must be passed in separately * from the event, due to the requirement to reverse the mobile mappings lookup in the top-level @@ -225,6 +227,7 @@ class DemoMobileConnectionRepository( _resolvedNetworkType.value = resolvedNetworkType isAllowedDuringAirplaneMode.value = false + hasPrioritizedNetworkCapabilities.value = event.slice } fun processCarrierMergedEvent(event: FakeWifiEventModel.CarrierMerged) { @@ -250,6 +253,7 @@ class DemoMobileConnectionRepository( _isGsm.value = false _carrierNetworkChangeActive.value = false isAllowedDuringAirplaneMode.value = true + hasPrioritizedNetworkCapabilities.value = false } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt index d4ddb856eecf226beb4bd8588eccdcc3b0dbb63d..4cd877eb1a14662298970f49d2e6b6464994b047 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt @@ -76,6 +76,7 @@ constructor( val carrierNetworkChange = getString("carriernetworkchange") == "show" val roaming = getString("roam") == "show" val name = getString("networkname") ?: "demo mode" + val slice = getString("slice").toBoolean() return Mobile( level = level, @@ -87,6 +88,7 @@ constructor( carrierNetworkChange = carrierNetworkChange, roaming = roaming, name = name, + slice = slice, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt index 8b03f71a27293b9a92d41295c117871d50356711..0aa95f8821cc9385a2640660d1b2b2be6bc9d4f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt @@ -36,6 +36,7 @@ sealed interface FakeNetworkEventModel { val carrierNetworkChange: Boolean, val roaming: Boolean, val name: String, + val slice: Boolean = false, ) : FakeNetworkEventModel data class MobileDisabled( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt index 28be3be289284ebf75d4965844dc81e31f9b8ca1..27edd1ec0caaacd391af628efe834e1270f93206 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt @@ -174,6 +174,13 @@ class CarrierMergedConnectionRepository( */ override val isAllowedDuringAirplaneMode = MutableStateFlow(true).asStateFlow() + /** + * It's not currently considered possible that a carrier merged network can have these + * prioritized capabilities. If we need to track them, we can add the same check as is in + * [MobileConnectionRepositoryImpl]. + */ + override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false).asStateFlow() + override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt index ee11c06ef3f5ab75fea912d30a8d972dd9e2239f..6b6192186e0ff50316d76428f1d2de55be69f08e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt @@ -309,6 +309,15 @@ class FullMobileConnectionRepository( activeRepo.value.isAllowedDuringAirplaneMode.value, ) + override val hasPrioritizedNetworkCapabilities = + activeRepo + .flatMapLatest { it.hasPrioritizedNetworkCapabilities } + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + activeRepo.value.hasPrioritizedNetworkCapabilities.value, + ) + class Factory @Inject constructor( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt index dc50990d002a28e953d1d5d77d7e9bf4b7a8137f..760dd7eba3cf70b181b0bd6197d546334cf4d70a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt @@ -21,6 +21,11 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.net.ConnectivityManager +import android.net.ConnectivityManager.NetworkCallback +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN import android.telephony.CellSignalStrengthCdma import android.telephony.ServiceState @@ -91,6 +96,7 @@ class MobileConnectionRepositoryImpl( subscriptionModel: StateFlow<SubscriptionModel?>, defaultNetworkName: NetworkNameModel, networkNameSeparator: String, + connectivityManager: ConnectivityManager, private val telephonyManager: TelephonyManager, systemUiCarrierConfig: SystemUiCarrierConfig, broadcastDispatcher: BroadcastDispatcher, @@ -374,11 +380,50 @@ class MobileConnectionRepositoryImpl( /** Typical mobile connections aren't available during airplane mode. */ override val isAllowedDuringAirplaneMode = MutableStateFlow(false).asStateFlow() + /** + * Currently, a network with NET_CAPABILITY_PRIORITIZE_LATENCY is the only type of network that + * we consider to be a "network slice". _PRIORITIZE_BANDWIDTH may be added in the future. Any of + * these capabilities that are used here must also be represented in the + * self_certified_network_capabilities.xml config file + */ + @SuppressLint("WrongConstant") + private val networkSliceRequest = + NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY) + .setSubscriptionIds(setOf(subId)) + .build() + + @SuppressLint("MissingPermission") + override val hasPrioritizedNetworkCapabilities: StateFlow<Boolean> = + conflatedCallbackFlow { + // Our network callback listens only for this.subId && net_cap_prioritize_latency + // therefore our state is a simple mapping of whether or not that network exists + val callback = + object : NetworkCallback() { + override fun onAvailable(network: Network) { + logger.logPrioritizedNetworkAvailable(network.netId) + trySend(true) + } + + override fun onLost(network: Network) { + logger.logPrioritizedNetworkLost(network.netId) + trySend(false) + } + } + + connectivityManager.registerNetworkCallback(networkSliceRequest, callback) + + awaitClose { connectivityManager.unregisterNetworkCallback(callback) } + } + .flowOn(bgDispatcher) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + class Factory @Inject constructor( private val context: Context, private val broadcastDispatcher: BroadcastDispatcher, + private val connectivityManager: ConnectivityManager, private val telephonyManager: TelephonyManager, private val logger: MobileInputLogger, private val carrierConfigRepository: CarrierConfigRepository, @@ -399,6 +444,7 @@ class MobileConnectionRepositoryImpl( subscriptionModel, defaultNetworkName, networkNameSeparator, + connectivityManager, telephonyManager.createForSubscriptionId(subId), carrierConfigRepository.getOrCreateConfigForSubId(subId), broadcastDispatcher, @@ -421,11 +467,17 @@ private fun Intent.carrierId(): Int = */ sealed interface CallbackEvent { data class OnCarrierNetworkChange(val active: Boolean) : CallbackEvent + data class OnDataActivity(val direction: Int) : CallbackEvent + data class OnDataConnectionStateChanged(val dataState: Int) : CallbackEvent + data class OnDataEnabledChanged(val enabled: Boolean) : CallbackEvent + data class OnDisplayInfoChanged(val telephonyDisplayInfo: TelephonyDisplayInfo) : CallbackEvent + data class OnServiceStateChanged(val serviceState: ServiceState) : CallbackEvent + data class OnSignalStrengthChanged(val signalStrength: SignalStrength) : CallbackEvent } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt index 4bf297cd088c34e31ab8c37b4dbc81923db2174f..fe49c07913707d0a0e4f1524b3f8c1ee6e4d1f9e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt @@ -76,6 +76,9 @@ interface MobileIconInteractor { /** Observable for RAT type (network type) indicator */ val networkTypeIconGroup: StateFlow<NetworkTypeIconModel> + /** Whether or not to show the slice attribution */ + val showSliceAttribution: StateFlow<Boolean> + /** * Provider name for this network connection. The name can be one of 3 values: * 1. The default network name, if one is configured @@ -238,6 +241,9 @@ class MobileIconInteractorImpl( DefaultIcon(defaultMobileIconGroup.value), ) + override val showSliceAttribution: StateFlow<Boolean> = + connectionRepository.hasPrioritizedNetworkCapabilities + override val isRoaming: StateFlow<Boolean> = combine( connectionRepository.carrierNetworkChangeActive, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt index f0470ca0a1ad21a7715d8e005f3a6ed0a34f7beb..b93e4437828010802da68f65c133fae9ad5349ee 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt @@ -16,11 +16,13 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.binder +import android.annotation.ColorInt import android.content.res.ColorStateList import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup +import android.widget.FrameLayout import android.widget.ImageView import android.widget.Space import androidx.core.view.isVisible @@ -28,10 +30,11 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.android.settingslib.graph.SignalDrawable -import com.android.systemui.res.R import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.plugins.DarkIconDispatcher +import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger @@ -43,6 +46,11 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch +private data class Colors( + @ColorInt val tint: Int, + @ColorInt val contrast: Int, +) + object MobileIconBinder { /** Binds the view to the view-model, continuing to update the former based on the latter */ @JvmStatic @@ -57,6 +65,7 @@ object MobileIconBinder { val activityIn = view.requireViewById<ImageView>(R.id.mobile_in) val activityOut = view.requireViewById<ImageView>(R.id.mobile_out) val networkTypeView = view.requireViewById<ImageView>(R.id.mobile_type) + val networkTypeContainer = view.requireViewById<FrameLayout>(R.id.mobile_type_container) val iconView = view.requireViewById<ImageView>(R.id.mobile_signal) val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) } val roamingView = view.requireViewById<ImageView>(R.id.mobile_roaming) @@ -70,7 +79,13 @@ object MobileIconBinder { @StatusBarIconView.VisibleState val visibilityState: MutableStateFlow<Int> = MutableStateFlow(initialVisibilityState) - val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) + val iconTint: MutableStateFlow<Colors> = + MutableStateFlow( + Colors( + tint = DarkIconDispatcher.DEFAULT_ICON_TINT, + contrast = DarkIconDispatcher.DEFAULT_INVERSE_ICON_TINT + ) + ) val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) var isCollecting = false @@ -139,7 +154,26 @@ object MobileIconBinder { dataTypeId, ) dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) } - networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE + networkTypeContainer.visibility = + if (dataTypeId != null) VISIBLE else GONE + } + } + + // Set the network type background + launch { + viewModel.networkTypeBackground.collect { background -> + networkTypeContainer.setBackgroundResource(background?.res ?: 0) + + // Tint will invert when this bit changes + if (background?.res != null) { + networkTypeContainer.backgroundTintList = + ColorStateList.valueOf(iconTint.value.tint) + networkTypeView.imageTintList = + ColorStateList.valueOf(iconTint.value.contrast) + } else { + networkTypeView.imageTintList = + ColorStateList.valueOf(iconTint.value.tint) + } } } @@ -164,14 +198,24 @@ object MobileIconBinder { // Set the tint launch { - iconTint.collect { tint -> - val tintList = ColorStateList.valueOf(tint) - iconView.imageTintList = tintList - networkTypeView.imageTintList = tintList - roamingView.imageTintList = tintList - activityIn.imageTintList = tintList - activityOut.imageTintList = tintList - dotView.setDecorColor(tint) + iconTint.collect { colors -> + val tint = ColorStateList.valueOf(colors.tint) + val contrast = ColorStateList.valueOf(colors.contrast) + + iconView.imageTintList = tint + + // If the bg is visible, tint it and use the contrast for the fg + if (viewModel.networkTypeBackground.value != null) { + networkTypeContainer.backgroundTintList = tint + networkTypeView.imageTintList = contrast + } else { + networkTypeView.imageTintList = tint + } + + roamingView.imageTintList = tint + activityIn.imageTintList = tint + activityOut.imageTintList = tint + dotView.setDecorColor(colors.tint) } } @@ -196,8 +240,8 @@ object MobileIconBinder { visibilityState.value = state } - override fun onIconTintChanged(newTint: Int) { - iconTint.value = newTint + override fun onIconTintChanged(newTint: Int, contrastTint: Int) { + iconTint.value = Colors(tint = newTint, contrast = contrastTint) } override fun onDecorTintChanged(newTint: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt index dfabeea960829f369ba71d5d78250ec6d6abe8a7..d88c9efdac768792d4c115138262b43a5ffa144f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt @@ -19,7 +19,10 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags.NEW_NETWORK_SLICE_UI import com.android.systemui.log.table.logDiffsForTable +import com.android.systemui.res.R import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor @@ -47,6 +50,8 @@ interface MobileIconViewModelCommon { val roaming: Flow<Boolean> /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */ val networkTypeIcon: Flow<Icon.Resource?> + /** The slice attribution. Drawn as a background layer */ + val networkTypeBackground: StateFlow<Icon.Resource?> val activityInVisible: Flow<Boolean> val activityOutVisible: Flow<Boolean> val activityContainerVisible: Flow<Boolean> @@ -67,12 +72,12 @@ interface MobileIconViewModelCommon { */ @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) -class MobileIconViewModel -constructor( +class MobileIconViewModel( override val subscriptionId: Int, iconInteractor: MobileIconInteractor, airplaneModeInteractor: AirplaneModeInteractor, constants: ConnectivityConstants, + flags: FeatureFlagsClassic, scope: CoroutineScope, ) : MobileIconViewModelCommon { override val isVisible: StateFlow<Boolean> = @@ -152,6 +157,20 @@ constructor( .distinctUntilChanged() .stateIn(scope, SharingStarted.WhileSubscribed(), null) + override val networkTypeBackground = + if (!flags.isEnabled(NEW_NETWORK_SLICE_UI)) { + flowOf(null) + } else { + iconInteractor.showSliceAttribution.map { + if (it) { + Icon.Resource(R.drawable.mobile_network_type_background, null) + } else { + null + } + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), null) + override val roaming: StateFlow<Boolean> = iconInteractor.isRoaming .logDiffsForTable( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt index 0f55910d87792f669a9f20a6fdcc41f41a3dc73b..be843ba8699fc68cff6cbe21b85bef1f5b891d26 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel import androidx.annotation.VisibleForTesting import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor @@ -54,6 +55,7 @@ constructor( private val interactor: MobileIconsInteractor, private val airplaneModeInteractor: AirplaneModeInteractor, private val constants: ConnectivityConstants, + private val flags: FeatureFlagsClassic, @Application private val scope: CoroutineScope, ) { @VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>() @@ -113,6 +115,7 @@ constructor( interactor.getMobileConnectionInteractorForSubId(subId), airplaneModeInteractor, constants, + flags, scope, ) .also { mobileIconSubIdCache[subId] = it } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt index 81f8683411caf098661cadf73cf22167cd116368..790596ea5520b21a05dcbcf376a8368be0eb6f88 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt @@ -32,8 +32,8 @@ interface ModernStatusBarViewBinding { /** Notifies that the visibility state has changed. */ fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) - /** Notifies that the icon tint has been updated. */ - fun onIconTintChanged(newTint: Int) + /** Notifies that the icon tint has been updated. Includes a contrast for layered drawables */ + fun onIconTintChanged(newTint: Int, contrastTint: Int) /** Notifies that the decor tint has been updated (used only for the dot). */ fun onDecorTintChanged(newTint: Int) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt index fe69d818dedba32351af442d3f8443c02de30fb7..3b87bed2e0efb04f80f388b673453dfe6e50e44b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt @@ -20,8 +20,8 @@ import android.content.Context import android.graphics.Rect import android.util.AttributeSet import android.view.Gravity -import com.android.systemui.res.R import com.android.systemui.plugins.DarkIconDispatcher +import com.android.systemui.res.R import com.android.systemui.statusbar.BaseStatusBarFrameLayout import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT @@ -51,13 +51,23 @@ open class ModernStatusBarView(context: Context, attrs: AttributeSet?) : override fun getSlot() = slot override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) { + // nop + } + + override fun onDarkChangedWithContrast(areas: ArrayList<Rect>, tint: Int, contrastTint: Int) { val newTint = DarkIconDispatcher.getTint(areas, this, tint) - binding.onIconTintChanged(newTint) + val contrast = DarkIconDispatcher.getInverseTint(areas, this, contrastTint) + + binding.onIconTintChanged(newTint, contrast) binding.onDecorTintChanged(newTint) } override fun setStaticDrawableColor(color: Int) { - binding.onIconTintChanged(color) + // nop + } + + override fun setStaticDrawableColor(color: Int, foregroundColor: Int) { + binding.onIconTintChanged(color, foregroundColor) } override fun setDecorColor(color: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt index a9ac51d7c47230a46143fea992cce7cfcbacee47..60055279055ca12fdef910755f8b22eadb4dd398 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt @@ -23,9 +23,9 @@ import android.widget.ImageView import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.res.R import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding @@ -165,7 +165,7 @@ object WifiViewBinder { visibilityState.value = state } - override fun onIconTintChanged(newTint: Int) { + override fun onIconTintChanged(newTint: Int, contrastTint: Int /* unused */) { iconTint.value = newTint } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt deleted file mode 100644 index 712eef13421bf26b38814fdcc6b90d9983d9f2f2..0000000000000000000000000000000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics.domain.interactor - -import android.hardware.biometrics.SensorLocationInternal -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository -import com.android.systemui.biometrics.shared.model.FingerprintSensorType -import com.android.systemui.biometrics.shared.model.SensorStrength -import com.android.systemui.coroutines.collectLastValue -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.mockito.junit.MockitoJUnit - -@SmallTest -@RunWith(JUnit4::class) -class SideFpsOverlayInteractorTest : SysuiTestCase() { - - @JvmField @Rule var mockitoRule = MockitoJUnit.rule() - private lateinit var testScope: TestScope - - private val fingerprintRepository = FakeFingerprintPropertyRepository() - - private lateinit var interactor: SideFpsOverlayInteractor - - @Before - fun setup() { - testScope = TestScope(StandardTestDispatcher()) - interactor = SideFpsOverlayInteractorImpl(fingerprintRepository) - } - - @Test - fun testOverlayOffsetUpdates() = - testScope.runTest { - fingerprintRepository.setProperties( - sensorId = 1, - strength = SensorStrength.STRONG, - sensorType = FingerprintSensorType.REAR, - sensorLocations = - mapOf( - "" to - SensorLocationInternal( - "" /* displayId */, - 540 /* sensorLocationX */, - 1636 /* sensorLocationY */, - 130 /* sensorRadius */ - ), - "display_id_1" to - SensorLocationInternal( - "display_id_1" /* displayId */, - 100 /* sensorLocationX */, - 300 /* sensorLocationY */, - 20 /* sensorRadius */ - ) - ) - ) - - val displayId by collectLastValue(interactor.displayId) - val offsets by collectLastValue(interactor.overlayOffsets) - - // Assert offsets of empty displayId. - assertThat(displayId).isEqualTo("") - assertThat(offsets?.displayId).isEqualTo("") - assertThat(offsets?.sensorLocationX).isEqualTo(540) - assertThat(offsets?.sensorLocationY).isEqualTo(1636) - assertThat(offsets?.sensorRadius).isEqualTo(130) - - // Offsets should be updated correctly. - interactor.onDisplayChanged("display_id_1") - assertThat(displayId).isEqualTo("display_id_1") - assertThat(offsets?.displayId).isEqualTo("display_id_1") - assertThat(offsets?.sensorLocationX).isEqualTo(100) - assertThat(offsets?.sensorLocationY).isEqualTo(300) - assertThat(offsets?.sensorRadius).isEqualTo(20) - - // Should return default offset when the displayId is invalid. - interactor.onDisplayChanged("invalid_display_id") - assertThat(displayId).isEqualTo("invalid_display_id") - assertThat(offsets?.displayId).isEqualTo(SensorLocationInternal.DEFAULT.displayId) - assertThat(offsets?.sensorLocationX) - .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationX) - assertThat(offsets?.sensorLocationY) - .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationY) - assertThat(offsets?.sensorRadius).isEqualTo(SensorLocationInternal.DEFAULT.sensorRadius) - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..99501c426e0c1cb3df572a53289fb13f871ce459 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.domain.interactor + +import android.graphics.Rect +import android.hardware.biometrics.SensorLocationInternal +import android.hardware.display.DisplayManagerGlobal +import android.view.Display +import android.view.DisplayInfo +import android.view.WindowInsets +import android.view.WindowManager +import android.view.WindowMetrics +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.biometrics.shared.model.DisplayRotation +import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_0 +import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_180 +import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_270 +import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_90 +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.SensorStrength +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags.REST_TO_UNLOCK +import com.android.systemui.res.R +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.Mockito.spy +import org.mockito.junit.MockitoJUnit + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class SideFpsSensorInteractorTest : SysuiTestCase() { + + @JvmField @Rule var mockitoRule = MockitoJUnit.rule() + private lateinit var testScope: TestScope + + private val fingerprintRepository = FakeFingerprintPropertyRepository() + + private lateinit var underTest: SideFpsSensorInteractor + + @Mock private lateinit var windowManager: WindowManager + @Mock private lateinit var displayStateInteractor: DisplayStateInteractor + + private val contextDisplayInfo = DisplayInfo() + private val displayChangeEvent = MutableStateFlow(0) + private val currentRotation = MutableStateFlow(ROTATION_0) + + @Before + fun setup() { + testScope = TestScope(StandardTestDispatcher()) + mContext = spy(mContext) + + val displayManager = mock(DisplayManagerGlobal::class.java) + val resources = mContext.resources + whenever(mContext.display) + .thenReturn(Display(displayManager, 1, contextDisplayInfo, resources)) + whenever(displayStateInteractor.displayChanges).thenReturn(displayChangeEvent) + whenever(displayStateInteractor.currentRotation).thenReturn(currentRotation) + + contextDisplayInfo.uniqueId = "current-display" + + underTest = + SideFpsSensorInteractor( + mContext, + fingerprintRepository, + windowManager, + displayStateInteractor, + FakeFeatureFlagsClassic().apply { set(REST_TO_UNLOCK, true) } + ) + } + + @Test + fun testSfpsSensorAvailable() = + testScope.runTest { + val isAvailable by collectLastValue(underTest.isAvailable) + + setupFingerprint(FingerprintSensorType.POWER_BUTTON) + assertThat(isAvailable).isTrue() + + setupFingerprint(FingerprintSensorType.HOME_BUTTON) + assertThat(isAvailable).isFalse() + + setupFingerprint(FingerprintSensorType.REAR) + assertThat(isAvailable).isFalse() + + setupFingerprint(FingerprintSensorType.UDFPS_OPTICAL) + assertThat(isAvailable).isFalse() + + setupFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC) + assertThat(isAvailable).isFalse() + + setupFingerprint(FingerprintSensorType.UNKNOWN) + assertThat(isAvailable).isFalse() + } + + @Test + fun authenticationDurationIsAvailableWhenSFPSSensorIsAvailable() = + testScope.runTest { + assertThat(collectLastValue(underTest.authenticationDuration)()) + .isEqualTo(context.resources.getInteger(R.integer.config_restToUnlockDuration)) + } + + @Test + fun verticalSensorLocationIsAdjustedToScreenPositionForRotation0() = + testScope.runTest { + /* + (0,0) (1000,0) + ------------------ + | ^^^^^ | (1000, 200) + | status bar || <--- start of sensor at Rotation_0 + | || <--- end of sensor + | | (1000, 300) + | | + ------------------ (1000, 800) + */ + setupFPLocationAndDisplaySize( + width = 1000, + height = 800, + rotation = ROTATION_0, + sensorLocationY = 200, + sensorWidth = 100, + ) + + val sensorLocation by collectLastValue(underTest.sensorLocation) + assertThat(sensorLocation!!.left).isEqualTo(1000) + assertThat(sensorLocation!!.top).isEqualTo(200) + assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true) + assertThat(sensorLocation!!.width).isEqualTo(100) + } + + @Test + fun verticalSensorLocationIsAdjustedToScreenPositionForRotation270() = + testScope.runTest { + /* + (800,0) (800, 1000) + --------------------- + | | (600, 1000) + | < || <--- end of sensor at Rotation_270 + | < status bar || <--- start of sensor + | < | (500, 1000) + | < | + (0,0) --------------------- + */ + setupFPLocationAndDisplaySize( + width = 800, + height = 1000, + rotation = ROTATION_270, + sensorLocationY = 200, + sensorWidth = 100, + ) + + val sensorLocation by collectLastValue(underTest.sensorLocation) + assertThat(sensorLocation!!.left).isEqualTo(500) + assertThat(sensorLocation!!.top).isEqualTo(1000) + assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true) + assertThat(sensorLocation!!.width).isEqualTo(100) + } + + @Test + fun verticalSensorLocationIsAdjustedToScreenPositionForRotation90() = + testScope.runTest { + /* + (0,0) + --------------------- + | | (200, 0) + | > || <--- end of sensor at Rotation_270 + | status bar > || <--- start of sensor + | > | (300, 0) + | > | + (800,1000) --------------------- + */ + setupFPLocationAndDisplaySize( + width = 800, + height = 1000, + rotation = ROTATION_90, + sensorLocationY = 200, + sensorWidth = 100, + ) + + val sensorLocation by collectLastValue(underTest.sensorLocation) + assertThat(sensorLocation!!.left).isEqualTo(200) + assertThat(sensorLocation!!.top).isEqualTo(0) + assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true) + assertThat(sensorLocation!!.width).isEqualTo(100) + } + + @Test + fun verticalSensorLocationIsAdjustedToScreenPositionForRotation180() = + testScope.runTest { + /* + + (1000,800) --------------------- + | | (0, 600) + | || <--- end of sensor at Rotation_270 + | status bar || <--- start of sensor + | \/\/\/\/\/\/\/ | (0, 500) + | | + --------------------- (0,0) + */ + setupFPLocationAndDisplaySize( + width = 1000, + height = 800, + rotation = ROTATION_180, + sensorLocationY = 200, + sensorWidth = 100, + ) + + val sensorLocation by collectLastValue(underTest.sensorLocation) + assertThat(sensorLocation!!.left).isEqualTo(0) + assertThat(sensorLocation!!.top).isEqualTo(500) + } + + @Test + fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation0() = + testScope.runTest { + /* + (0,0) (500,0) (600,0) (1000,0) + ____________===_________ + | | + | ^^^^^ | + | status bar | + | | + ------------------------ (1000, 800) + */ + setupFPLocationAndDisplaySize( + width = 1000, + height = 800, + rotation = ROTATION_0, + sensorLocationX = 500, + sensorWidth = 100, + ) + + val sensorLocation by collectLastValue(underTest.sensorLocation) + assertThat(sensorLocation!!.left).isEqualTo(500) + assertThat(sensorLocation!!.top).isEqualTo(0) + assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false) + assertThat(sensorLocation!!.width).isEqualTo(100) + } + + @Test + fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation90() = + testScope.runTest { + /* + (0,1000) (0,500) (0,400) (0,0) + ____________===_________ + | | + | > | + | status bar > | + | > | + (800, 1000) ------------------------ + */ + setupFPLocationAndDisplaySize( + width = 800, + height = 1000, + rotation = ROTATION_90, + sensorLocationX = 500, + sensorWidth = 100, + ) + + val sensorLocation by collectLastValue(underTest.sensorLocation) + assertThat(sensorLocation!!.left).isEqualTo(0) + assertThat(sensorLocation!!.top).isEqualTo(400) + assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false) + assertThat(sensorLocation!!.width).isEqualTo(100) + } + + @Test + fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation180() = + testScope.runTest { + /* + (1000, 800) (500, 800) (400, 800) (0,800) + ____________===_________ + | | + | | + | status bar | + | \/ \/ \/ \/ \/ \/ \/ | + ------------------------ (0,0) + */ + setupFPLocationAndDisplaySize( + width = 1000, + height = 800, + rotation = ROTATION_180, + sensorLocationX = 500, + sensorWidth = 100, + ) + + val sensorLocation by collectLastValue(underTest.sensorLocation) + assertThat(sensorLocation!!.left).isEqualTo(400) + assertThat(sensorLocation!!.top).isEqualTo(800) + assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false) + assertThat(sensorLocation!!.width).isEqualTo(100) + } + + @Test + fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation270() = + testScope.runTest { + /* + (800, 500) (800, 600) + (800, 0) ____________===_________ (800,1000) + | < | + | < | + | < status bar | + | < | + (0,0) ------------------------ + */ + setupFPLocationAndDisplaySize( + width = 800, + height = 1000, + rotation = ROTATION_270, + sensorLocationX = 500, + sensorWidth = 100, + ) + + val sensorLocation by collectLastValue(underTest.sensorLocation) + assertThat(sensorLocation!!.left).isEqualTo(800) + assertThat(sensorLocation!!.top).isEqualTo(500) + assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false) + assertThat(sensorLocation!!.width).isEqualTo(100) + } + + private suspend fun TestScope.setupFPLocationAndDisplaySize( + width: Int, + height: Int, + sensorLocationX: Int = 0, + sensorLocationY: Int = 0, + rotation: DisplayRotation, + sensorWidth: Int + ) { + overrideResource(R.integer.config_sfpsSensorWidth, sensorWidth) + setupDisplayDimensions(width, height) + currentRotation.value = rotation + setupFingerprint(x = sensorLocationX, y = sensorLocationY, displayId = "expanded_display") + } + + private fun setupDisplayDimensions(displayWidth: Int, displayHeight: Int) { + whenever(windowManager.maximumWindowMetrics) + .thenReturn( + WindowMetrics( + Rect(0, 0, displayWidth, displayHeight), + mock(WindowInsets::class.java) + ) + ) + } + + private suspend fun TestScope.setupFingerprint( + fingerprintSensorType: FingerprintSensorType = FingerprintSensorType.POWER_BUTTON, + x: Int = 0, + y: Int = 0, + displayId: String = "display_id_1" + ) { + contextDisplayInfo.uniqueId = displayId + fingerprintRepository.setProperties( + sensorId = 1, + strength = SensorStrength.STRONG, + sensorType = fingerprintSensorType, + sensorLocations = + mapOf( + "someOtherDisplayId" to + SensorLocationInternal( + "someOtherDisplayId", + x + 100, + y + 100, + 0, + ), + displayId to + SensorLocationInternal( + displayId, + x, + y, + 0, + ) + ) + ) + // Emit a display change event, this happens whenever any display related change happens, + // rotation, active display changing etc, display switched off/on. + displayChangeEvent.emit(1) + + runCurrent() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt index 37c70d8f25e0d4b192d88890c6a0cf4966ba3d8e..2bd2bff8095150aa9d8889aebd2574b7c2f3cc3f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt @@ -43,10 +43,10 @@ class FakeFeatureFlagsTest : SysuiTestCase() { fun accessingUnspecifiedFlags_throwsException() { val flags: FeatureFlags = FakeFeatureFlags() try { - assertThat(flags.isEnabled(Flags.TEAMFOOD)).isFalse() + assertThat(flags.isEnabled(Flags.NULL_FLAG)).isFalse() fail("Expected an exception when accessing an unspecified flag.") } catch (ex: IllegalStateException) { - assertThat(ex.message).contains("UNKNOWN(teamfood)") + assertThat(ex.message).contains("UNKNOWN(null_flag)") } try { assertThat(flags.isEnabled(unreleasedFlag)).isFalse() diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt index c12a581fb2c1bb1280a6c514b6d852fab05323ac..f51745b9f17c756f679d7065a06ea2553ddba179 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt @@ -22,6 +22,7 @@ import android.content.pm.PackageManager.NameNotFoundException import android.content.res.Resources import android.content.res.Resources.NotFoundException import android.test.suitebuilder.annotation.SmallTest +import com.android.systemui.FakeFeatureFlagsImpl import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq @@ -65,6 +66,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { private lateinit var broadcastReceiver: BroadcastReceiver private lateinit var clearCacheAction: Consumer<String> private val serverFlagReader = ServerFlagReaderFake() + private val fakeGantryFlags = FakeFeatureFlagsImpl() private val teamfoodableFlagA = UnreleasedFlag(name = "a", namespace = "test", teamfood = true) private val teamfoodableFlagB = ReleasedFlag(name = "b", namespace = "test", teamfood = true) @@ -72,7 +74,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) - flagMap.put(Flags.TEAMFOOD.name, Flags.TEAMFOOD) + fakeGantryFlags.setFlag("com.android.systemui.sysui_teamfood", false) flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA) flagMap.put(teamfoodableFlagB.name, teamfoodableFlagB) mFeatureFlagsClassicDebug = @@ -84,6 +86,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { resources, serverFlagReader, flagMap, + fakeGantryFlags, restarter ) mFeatureFlagsClassicDebug.init() @@ -121,8 +124,6 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { @Test fun teamFoodFlag_False() { - whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any())) - .thenReturn(false) assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isFalse() assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isTrue() @@ -133,8 +134,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { @Test fun teamFoodFlag_True() { - whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any())) - .thenReturn(true) + fakeGantryFlags.setFlag("com.android.systemui.sysui_teamfood", true) assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue() assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isTrue() @@ -149,8 +149,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { .thenReturn(true) whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.name), any())) .thenReturn(false) - whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any())) - .thenReturn(true) + fakeGantryFlags.setFlag("com.android.systemui.sysui_teamfood", true) assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue() assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isFalse() diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java index e537131bad01749c21c1e63661b392ce44f382b7..4c5a2144941a72902c674d9d2f47a51f449619b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java @@ -54,11 +54,7 @@ import com.android.systemui.statusbar.connectivity.NetworkController; import com.android.systemui.statusbar.connectivity.SignalCallback; import com.android.systemui.statusbar.connectivity.WifiIndicators; import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository; -import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository; import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor; -import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl; -import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel; -import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.CastController.CastDevice; import com.android.systemui.statusbar.policy.HotspotController; @@ -111,7 +107,8 @@ public class CastTileTest extends SysuiTestCase { private WifiInteractor mWifiInteractor; private final TileJavaAdapter mJavaAdapter = new TileJavaAdapter(); - private final FakeWifiRepository mWifiRepository = new FakeWifiRepository(); + private final FakeConnectivityRepository mConnectivityRepository = + new FakeConnectivityRepository(); private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); private final TestScope mTestScope = TestScopeProvider.getTestScope(); @@ -124,12 +121,6 @@ public class CastTileTest extends SysuiTestCase { mTestableLooper = TestableLooper.get(this); when(mHost.getContext()).thenReturn(mContext); - - mWifiInteractor = new WifiInteractorImpl( - new FakeConnectivityRepository(), - mWifiRepository, - mTestScope - ); } @After @@ -204,24 +195,40 @@ public class CastTileTest extends SysuiTestCase { // SIGNAL_CALLBACK_DEPRECATION flag set to true @Test - public void stateUnavailable_wifiDisabled_newPipeline() { + public void stateUnavailable_noDefaultNetworks_newPipeline() { createAndStartTileNewImpl(); - mWifiRepository.setIsWifiEnabled(false); mTestableLooper.processAllMessages(); assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state); } @Test - public void stateUnavailable_wifiEnabled_notConnected_newPipeline() { + public void stateUnavailable_mobileConnected_newPipeline() { createAndStartTileNewImpl(); - mWifiRepository.setIsWifiEnabled(true); - mWifiRepository.setWifiNetwork(Inactive.INSTANCE); + mConnectivityRepository.setMobileConnected(true); mTestableLooper.processAllMessages(); assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state); } + @Test + public void stateInactive_wifiConnected_newPipeline() { + createAndStartTileNewImpl(); + mConnectivityRepository.setWifiConnected(true); + mTestableLooper.processAllMessages(); + + assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state); + } + + @Test + public void stateInactive_ethernetConnected_newPipeline() { + createAndStartTileNewImpl(); + mConnectivityRepository.setEthernetConnected(true); + mTestableLooper.processAllMessages(); + + assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state); + } + @Test public void stateActive_wifiConnectedAndCasting_newPipeline() { createAndStartTileNewImpl(); @@ -231,40 +238,27 @@ public class CastTileTest extends SysuiTestCase { devices.add(device); when(mController.getCastDevices()).thenReturn(devices); - mWifiRepository.setWifiNetwork( - new WifiNetworkModel.Active( - 1 /* networkId */, - true /* isValidated */, - 3 /* level */, - "test" /* ssid */, - WifiNetworkModel.HotspotDeviceType.NONE, - false /* isPasspointAccessPoint */, - false /* isOnlineSignUpforPasspointAccessPoint */, - null /* passpointProviderFriendlyName */ - )); + mConnectivityRepository.setWifiConnected(true); + mTestableLooper.processAllMessages(); assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state); } @Test - public void stateInactive_wifiConnectedNotCasting_newPipeline() { + public void stateActive_ethernetConnectedAndCasting_newPipeline() { createAndStartTileNewImpl(); + CastController.CastDevice device = new CastController.CastDevice(); + device.state = CastDevice.STATE_CONNECTED; + List<CastDevice> devices = new ArrayList<>(); + devices.add(device); + when(mController.getCastDevices()).thenReturn(devices); + + mConnectivityRepository.setEthernetConnected(true); - mWifiRepository.setWifiNetwork( - new WifiNetworkModel.Active( - 1 /* networkId */, - true /* isValidated */, - 3 /* level */, - "test" /* ssid */, - WifiNetworkModel.HotspotDeviceType.NONE, - false /* isPasspointAccessPoint */, - false /* isOnlineSignUpforPasspointAccessPoint */, - null /* passpointProviderFriendlyName */ - )); mTestableLooper.processAllMessages(); - assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state); + assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state); } // ------------------------------------------------- @@ -512,7 +506,7 @@ public class CastTileTest extends SysuiTestCase { mNetworkController, mHotspotController, mDialogLaunchAnimator, - mWifiInteractor, + mConnectivityRepository, mJavaAdapter, mFeatureFlags ); @@ -555,7 +549,7 @@ public class CastTileTest extends SysuiTestCase { mNetworkController, mHotspotController, mDialogLaunchAnimator, - mWifiInteractor, + mConnectivityRepository, mJavaAdapter, mFeatureFlags ); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index 58e36be2b9fd77f2a717228799c987c7deed4f95..10c7c439e5f481e3f2e2169effd636d7f0ad5a36 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -21,6 +21,8 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel @@ -49,6 +51,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { private val testScope = utils.testScope private val sceneInteractor = utils.sceneInteractor() private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) + private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } private var mobileIconsViewModel: MobileIconsViewModel = MobileIconsViewModel( @@ -61,6 +64,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { FakeConnectivityRepository(), ), constants = mock(), + flags, scope = testScope.backgroundScope, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 61dd69a8126b5bdaac92d800b3b8083f07a1fdf2..88a5c17f4994bdd26ad0a3d0e9fba117d0f6f8c5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -25,6 +25,8 @@ import com.android.systemui.authentication.data.repository.FakeAuthenticationRep import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel import com.android.systemui.model.SysUiState @@ -137,6 +139,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { ) private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) + private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } private var mobileIconsViewModel: MobileIconsViewModel = MobileIconsViewModel( @@ -149,6 +152,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { FakeConnectivityRepository(), ), constants = mock(), + flags, scope = testScope.backgroundScope, ) 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 3ae1f35b8134e0866bd3379f39e544fdb77f967c..da4dc850fcaee190b0ae99bb805ef314b68e8b44 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 @@ -110,6 +111,15 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() { assertThat(dialog.isShowing).isTrue() } + @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/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt index a105c15b630aa4f9ff116f777deec4126b7523be..d8821aa6aa00d4d90dd80a22c270fa55cb60e860 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt @@ -63,8 +63,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { @Before fun setUp() { - whenever(controllerFactory.create(eq(0))).thenReturn(controller0) - whenever(controllerFactory.create(eq(1))).thenReturn(controller1) + whenever(controllerFactory.create(eq(0), any())).thenReturn(controller0) + whenever(controllerFactory.create(eq(1), any())).thenReturn(controller1) } @Test @@ -74,8 +74,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { val onSaved = { _: Uri -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) - verify(controllerFactory).create(eq(0)) - verify(controllerFactory).create(eq(1)) + verify(controllerFactory).create(eq(0), any()) + verify(controllerFactory).create(eq(1), any()) val capturer = ArgumentCaptor<ScreenshotData>() @@ -107,8 +107,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { callback ) - verify(controllerFactory).create(eq(0)) - verify(controllerFactory, never()).create(eq(1)) + verify(controllerFactory).create(eq(0), any()) + verify(controllerFactory, never()).create(eq(1), any()) val capturer = ArgumentCaptor<ScreenshotData>() @@ -139,7 +139,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { @Test fun executeScreenshots_allowedTypes_allCaptured() = testScope.runTest { - whenever(controllerFactory.create(any())).thenReturn(controller0) + whenever(controllerFactory.create(any(), any())).thenReturn(controller0) setDisplays( display(TYPE_INTERNAL, id = 0), diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt index 6205d90197a2628558bb8a7fe51eaa72623133dd..5091a7004f79a8874881987701f0e034d99b9833 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt @@ -86,7 +86,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() { ) .thenReturn(false) whenever(userManager.isUserUnlocked).thenReturn(true) - whenever(controllerFactory.create(any())).thenReturn(controller) + whenever(controllerFactory.create(any(), any())).thenReturn(controller) // Stub request processor as a synchronous no-op for tests with the flag enabled doAnswer { 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 8d8c70e26ab28fa0d7f52e3d64f21f4681d0fc29..700c9ae9006ab1b99d9f1afadd1d8e8c95ab3bb5 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; @@ -157,6 +161,7 @@ import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController; import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm; import com.android.systemui.statusbar.phone.KeyguardStatusBarView; import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController; import com.android.systemui.statusbar.phone.LightBarController; @@ -167,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; @@ -175,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; @@ -196,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 { @@ -323,16 +333,19 @@ 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; @Mock private SharedNotificationContainerInteractor mSharedNotificationContainerInteractor; + @Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm; protected final int mMaxUdfpsBurnInOffsetY = 5; 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; @@ -368,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); @@ -530,7 +564,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mDumpManager, mock(HeadsUpManager.class), new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager, - mInteractionJankMonitor, mShadeExpansionStateManager), + mInteractionJankMonitor, + () -> mShadeInteractor), mKeyguardBypassController, mDozeParameters, mScreenOffAnimationController, @@ -667,7 +702,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mKeyguardViewConfigurator, mKeyguardFaceAuthInteractor, new ResourcesSplitShadeStateController(), - mPowerInteractor); + mPowerInteractor, + mKeyguardClockPositionAlgorithm); mNotificationPanelViewController.initDependencies( mCentralSurfaces, null, 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 eb006100d5356d15a032193d682614a8826ae2a0..d4fb387f9670d72f44bb6baca3006d1010b9a8ed 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 65174bab7f638e6106a50e34cb8b5ef45467b74c..8a876c80194d7b6c3c7ad4c34f3ac09e272defaf 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/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt index 607cdab12f56b72cc3e19d4d3fc25fe404344010..df38f936f4f2746c96277ff8e1180f4e5de66c8d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt @@ -4,6 +4,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey @@ -31,6 +33,7 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { private val sceneInteractor = utils.sceneInteractor() private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) + private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } private var mobileIconsViewModel: MobileIconsViewModel = MobileIconsViewModel( @@ -43,6 +46,7 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { FakeConnectivityRepository(), ), constants = mock(), + flags, scope = testScope.backgroundScope, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index c4237827c3ea8d0ecf164ab6bc3e25f5687bb733..3064f4b2b355619fc870c2b9e3876f0561511ced 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -21,6 +21,8 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel @@ -55,6 +57,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { ) private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) + private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } private var mobileIconsViewModel: MobileIconsViewModel = MobileIconsViewModel( @@ -67,6 +70,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { FakeConnectivityRepository(), ), constants = mock(), + flags, scope = testScope.backgroundScope, ) 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 0000000000000000000000000000000000000000..3a9c24a7109cb9f3b5e0922fef2e73afff06ed89 --- /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 19863ecaf7230e8a655fbd443990dae9e4bdce38..a5f5fc7e36f2afc46a6d74afb84f00a964d0d26a 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 3327e42b930f0875ad7b2eee8aa1baade363ce67..1b26e19aa4a7dda711a817721f2aa4f8f35bf949 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 83f2a5d4fbdf7a4f15cd629171f96a80c8558830..b922ab39912bd62561a5ed4e787019e017a10c9a 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); @@ -505,6 +560,54 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); } + @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 @@ -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 cda2a74609bd5b0b4acc5856b71f18df00a0f255..48b95d40724665fcfc46d4ab97ac3104ef4d97cd 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/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java index 03f5f005ee47d1936ff017a93619fa14f5fa6f25..3556703a2fa87c36e0c9350353fb31d82c7b1abc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java @@ -30,9 +30,11 @@ import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.doze.util.BurnInHelperKt; +import com.android.systemui.log.LogBuffer; +import com.android.systemui.log.core.FakeLogBuffer; +import com.android.systemui.res.R; import org.junit.After; import org.junit.Before; @@ -51,8 +53,7 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { private static final float OPAQUE = 1.f; private static final float TRANSPARENT = 0.f; - @Mock - private Resources mResources; + @Mock private Resources mResources; private KeyguardClockPositionAlgorithm mClockPositionAlgorithm; private KeyguardClockPositionAlgorithm.Result mClockPosition; @@ -80,7 +81,8 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { .mockStatic(BurnInHelperKt.class) .startMocking(); - mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm(); + LogBuffer logBuffer = FakeLogBuffer.Factory.Companion.create(); + mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm(logBuffer); when(mResources.getDimensionPixelSize(anyInt())).thenReturn(0); mClockPositionAlgorithm.loadDimens(mResources); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt index 812e91beee486ef3920ef9ca9198f0bcf13fd60d..610fede3dfea7007c63a32653e3b7f6a41731395 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt @@ -60,6 +60,8 @@ class FakeMobileConnectionRepository( override val isAllowedDuringAirplaneMode = MutableStateFlow(false) + override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false) + fun setDataEnabled(enabled: Boolean) { _dataEnabled.value = enabled } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt index 57f97ec66a0042b7c89aad006fb8afa6b2816263..1f8cc54211e6fbfc32fb48f26c96eb655c7f090a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt @@ -123,6 +123,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC carrierNetworkChange = testCase.carrierNetworkChange, roaming = testCase.roaming, name = "demo name", + slice = testCase.slice, ) fakeNetworkEventFlow.value = networkModel @@ -142,6 +143,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC launch { conn.carrierName.collect {} } launch { conn.isEmergencyOnly.collect {} } launch { conn.dataConnectionState.collect {} } + launch { conn.hasPrioritizedNetworkCapabilities.collect {} } } return job } @@ -165,6 +167,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC .isEqualTo(NetworkNameModel.IntentDerived(model.name)) assertThat(conn.carrierName.value) .isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}")) + assertThat(conn.hasPrioritizedNetworkCapabilities.value).isEqualTo(model.slice) // TODO(b/261029387): check these once we start handling them assertThat(conn.isEmergencyOnly.value).isFalse() @@ -190,6 +193,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC val carrierNetworkChange: Boolean, val roaming: Boolean, val name: String, + val slice: Boolean, ) { override fun toString(): String { return "INPUT(level=$level, " + @@ -200,7 +204,8 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC "activity=$activity, " + "carrierNetworkChange=$carrierNetworkChange, " + "roaming=$roaming, " + - "name=$name)" + "name=$name," + + "slice=$slice)" } // Convenience for iterating test data and creating new cases @@ -214,6 +219,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC carrierNetworkChange: Boolean? = null, roaming: Boolean? = null, name: String? = null, + slice: Boolean? = null, ): TestCase = TestCase( level = level ?: this.level, @@ -225,6 +231,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC carrierNetworkChange = carrierNetworkChange ?: this.carrierNetworkChange, roaming = roaming ?: this.roaming, name = name ?: this.name, + slice = slice ?: this.slice, ) } @@ -254,6 +261,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC // false first so the base case doesn't have roaming set (more common) private val roaming = listOf(false, true) private val names = listOf("name 1", "name 2") + private val slice = listOf(false, true) @Parameters(name = "{0}") @JvmStatic fun data() = testData() @@ -291,6 +299,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC carrierNetworkChange.first(), roaming.first(), names.first(), + slice.first(), ) val tail = @@ -303,6 +312,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC carrierNetworkChange.map { baseCase.modifiedBy(carrierNetworkChange = it) }, roaming.map { baseCase.modifiedBy(roaming = it) }, names.map { baseCase.modifiedBy(name = it) }, + slice.map { baseCase.modifiedBy(slice = it) } ) .flatten() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt index 2712b70a97455546ecf6d35c326e437e1c07860a..d918fa8193bb21622c4601efa5d9b38a27779160 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt @@ -549,6 +549,7 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { launch { conn.carrierName.collect {} } launch { conn.isEmergencyOnly.collect {} } launch { conn.dataConnectionState.collect {} } + launch { conn.hasPrioritizedNetworkCapabilities.collect {} } } return job } @@ -574,6 +575,7 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { .isEqualTo(NetworkNameModel.IntentDerived(model.name)) assertThat(conn.carrierName.value) .isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}")) + assertThat(conn.hasPrioritizedNetworkCapabilities.value).isEqualTo(model.slice) // TODO(b/261029387) check these once we start handling them assertThat(conn.isEmergencyOnly.value).isFalse() @@ -599,6 +601,7 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { assertThat(conn.isEmergencyOnly.value).isFalse() assertThat(conn.isGsm.value).isFalse() assertThat(conn.dataConnectionState.value).isEqualTo(DataConnectionState.Connected) + assertThat(conn.hasPrioritizedNetworkCapabilities.value).isFalse() job.cancel() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt index ede02d1fbc9c4fc9be4452086302ad0bed750d71..1c21ebe05d84d66f99501bcb5101d06347459cf3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod +import android.net.ConnectivityManager import android.telephony.ServiceState import android.telephony.SignalStrength import android.telephony.TelephonyCallback @@ -80,6 +81,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { ) private val mobileFactory = mock<MobileConnectionRepositoryImpl.Factory>() private val carrierMergedFactory = mock<CarrierMergedConnectionRepository.Factory>() + private val connectivityManager = mock<ConnectivityManager>() private val subscriptionModel = MutableStateFlow( @@ -678,6 +680,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { subscriptionModel, DEFAULT_NAME_MODEL, SEP, + connectivityManager, telephonyManager, systemUiCarrierConfig = mock(), fakeBroadcastDispatcher, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt index 8ef82c9fe7bc4e8f0a158ff28cf814112ca22e8a..ba6426586ebd5e196f4707982855182bedefd151 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt @@ -19,6 +19,8 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.net.ConnectivityManager +import android.net.ConnectivityManager.NetworkCallback import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL @@ -85,7 +87,9 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityMod import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -107,6 +111,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { private lateinit var underTest: MobileConnectionRepositoryImpl private lateinit var connectionsRepo: FakeMobileConnectionsRepository + @Mock private lateinit var connectivityManager: ConnectivityManager @Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var logger: MobileInputLogger @Mock private lateinit var tableLogger: TableLogBuffer @@ -144,6 +149,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { subscriptionModel, DEFAULT_NAME_MODEL, SEP, + connectivityManager, telephonyManager, systemUiCarrierConfig, fakeBroadcastDispatcher, @@ -904,6 +910,45 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { assertThat(latest).isFalse() } + @Test + fun hasPrioritizedCaps_defaultFalse() { + assertThat(underTest.hasPrioritizedNetworkCapabilities.value).isFalse() + } + + @Test + fun hasPrioritizedCaps_trueWhenAvailable() = + testScope.runTest { + val latest by collectLastValue(underTest.hasPrioritizedNetworkCapabilities) + + val callback: NetworkCallback = + withArgCaptor<NetworkCallback> { + verify(connectivityManager).registerNetworkCallback(any(), capture()) + } + + callback.onAvailable(mock()) + + assertThat(latest).isTrue() + } + + @Test + fun hasPrioritizedCaps_becomesFalseWhenNetworkLost() = + testScope.runTest { + val latest by collectLastValue(underTest.hasPrioritizedNetworkCapabilities) + + val callback: NetworkCallback = + withArgCaptor<NetworkCallback> { + verify(connectivityManager).registerNetworkCallback(any(), capture()) + } + + callback.onAvailable(mock()) + + assertThat(latest).isTrue() + + callback.onLost(mock()) + + assertThat(latest).isFalse() + } + private inline fun <reified T> getTelephonyCallbackForType(): T { return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt index 852ed2054fcd581b09048a747e7b145b60b9aeac..889f60a08766ea723ab2a62cf59598e27d9ae3a8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod +import android.net.ConnectivityManager import android.telephony.ServiceState import android.telephony.TelephonyCallback import android.telephony.TelephonyCallback.CarrierNetworkListener @@ -96,6 +97,7 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() { private lateinit var underTest: MobileConnectionRepositoryImpl private lateinit var connectionsRepo: FakeMobileConnectionsRepository + @Mock private lateinit var connectivityManager: ConnectivityManager @Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var logger: MobileInputLogger @Mock private lateinit var tableLogger: TableLogBuffer @@ -129,6 +131,7 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() { subscriptionModel, DEFAULT_NAME, SEP, + connectivityManager, telephonyManager, systemUiCarrierConfig, fakeBroadcastDispatcher, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index 9148c758029687af11d8e368df009d87c119b981..18ba6c4f5d9f89e58f20a68635c9723b2ba0df49 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -180,6 +180,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { MobileConnectionRepositoryImpl.Factory( context, fakeBroadcastDispatcher, + connectivityManager, telephonyManager = telephonyManager, bgDispatcher = dispatcher, logger = logger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt index de2b6a850e0334363a711cc5623e605f47ab9281..5f4d7bf6f371eb574eafdd90e1f34571dcaed024 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt @@ -48,6 +48,8 @@ class FakeMobileIconInteractor( NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G) ) + override val showSliceAttribution = MutableStateFlow(false) + override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo mode")) override val carrierName = MutableStateFlow("demo mode") diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt index 218fd33ac0fc02325f61a0055010aba226d38533..3936bedc10594f13852cc34d0c3a65be14e3939b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt @@ -22,13 +22,15 @@ import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import android.testing.ViewUtils import android.view.View +import android.widget.FrameLayout import android.widget.ImageView import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView -import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor @@ -58,8 +60,8 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { private lateinit var testableLooper: TestableLooper private val testDispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(testDispatcher) + private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } - @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags @Mock private lateinit var tableLogBuffer: TableLogBuffer @Mock private lateinit var viewLogger: MobileViewLogger @Mock private lateinit var constants: ConnectivityConstants @@ -246,7 +248,8 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { testableLooper.processAllMessages() val color = 0x12345678 - view.onDarkChanged(arrayListOf(), 1.0f, color) + val contrast = 0x12344321 + view.onDarkChangedWithContrast(arrayListOf(), color, contrast) testableLooper.processAllMessages() assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color)) @@ -267,7 +270,8 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { testableLooper.processAllMessages() val color = 0x23456789 - view.setStaticDrawableColor(color) + val contrast = 0x12344321 + view.setStaticDrawableColor(color, contrast) testableLooper.processAllMessages() assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color)) @@ -275,6 +279,35 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { ViewUtils.detachView(view) } + @Test + fun colorChange_layersUpdateWithContrast() { + // Allow the slice, and set it to visible. This cause us to use special color logic + flags.set(Flags.NEW_NETWORK_SLICE_UI, true) + interactor.showSliceAttribution.value = true + createViewModel() + + val view = + ModernStatusBarMobileView.constructAndBind( + context, + viewLogger, + SLOT_NAME, + viewModel, + ) + ViewUtils.attachView(view) + testableLooper.processAllMessages() + + val color = 0x23456789 + val contrast = 0x12344321 + view.setStaticDrawableColor(color, contrast) + + testableLooper.processAllMessages() + + assertThat(view.getNetTypeContainer().backgroundTintList).isEqualTo(color.colorState()) + assertThat(view.getNetTypeView().imageTintList).isEqualTo(contrast.colorState()) + + ViewUtils.detachView(view) + } + private fun View.getGroupView(): View { return this.requireViewById(R.id.mobile_group) } @@ -283,10 +316,20 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { return this.requireViewById(R.id.mobile_signal) } + private fun View.getNetTypeContainer(): FrameLayout { + return this.requireViewById(R.id.mobile_type_container) + } + + private fun View.getNetTypeView(): ImageView { + return this.requireViewById(R.id.mobile_type) + } + private fun View.getDotView(): View { return this.requireViewById(R.id.status_bar_dot) } + private fun Int.colorState() = ColorStateList.valueOf(this) + private fun createViewModel() { viewModelCommon = MobileIconViewModel( @@ -294,6 +337,7 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { interactor, airplaneModeInteractor, constants, + flags, testScope.backgroundScope, ) viewModel = QsMobileIconViewModel(viewModelCommon) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt index 187832908e2beecd62d5b2eff4bb0719a5e655e1..1d5487f31e55e87aadff539393c5d66f290b6011 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt @@ -16,8 +16,11 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags @@ -47,12 +50,14 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) @SmallTest +@RunWith(AndroidJUnit4::class) class LocationBasedMobileIconViewModelTest : SysuiTestCase() { private lateinit var commonImpl: MobileIconViewModelCommon private lateinit var homeIcon: HomeMobileIconViewModel @@ -65,6 +70,7 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() { private lateinit var airplaneModeInteractor: AirplaneModeInteractor private val connectivityRepository = FakeConnectivityRepository() + private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags @Mock private lateinit var constants: ConnectivityConstants @@ -133,6 +139,7 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() { interactor, airplaneModeInteractor, constants, + flags, testScope.backgroundScope, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt index 796d5ec3dd421bc8167e466c71051ecb6f61e6a1..c831e62dd709addc53e6b8e08688f7346d882ad4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE @@ -26,7 +27,12 @@ import com.android.settingslib.mobile.TelephonyIcons.UNKNOWN import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags +import com.android.systemui.flags.Flags.NEW_NETWORK_SLICE_UI import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.res.R import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor @@ -54,12 +60,14 @@ import kotlinx.coroutines.test.runTest import kotlinx.coroutines.yield import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) @SmallTest +@RunWith(AndroidJUnit4::class) class MobileIconViewModelTest : SysuiTestCase() { private var connectivityRepository = FakeConnectivityRepository() @@ -74,6 +82,7 @@ class MobileIconViewModelTest : SysuiTestCase() { @Mock private lateinit var tableLogBuffer: TableLogBuffer @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker + private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } private val testDispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(testDispatcher) @@ -595,6 +604,46 @@ class MobileIconViewModelTest : SysuiTestCase() { containerJob.cancel() } + @Test + fun netTypeBackground_flagOff_isNull() = + testScope.runTest { + flags.set(NEW_NETWORK_SLICE_UI, false) + createAndSetViewModel() + + val latest by collectLastValue(underTest.networkTypeBackground) + + repository.hasPrioritizedNetworkCapabilities.value = true + + assertThat(latest).isNull() + } + + @Test + fun netTypeBackground_flagOn_nullWhenNoPrioritizedCapabilities() = + testScope.runTest { + flags.set(NEW_NETWORK_SLICE_UI, true) + createAndSetViewModel() + + val latest by collectLastValue(underTest.networkTypeBackground) + + repository.hasPrioritizedNetworkCapabilities.value = false + + assertThat(latest).isNull() + } + + @Test + fun netTypeBackground_flagOn_notNullWhenPrioritizedCapabilities() = + testScope.runTest { + flags.set(NEW_NETWORK_SLICE_UI, true) + createAndSetViewModel() + + val latest by collectLastValue(underTest.networkTypeBackground) + + repository.hasPrioritizedNetworkCapabilities.value = true + + assertThat(latest) + .isEqualTo(Icon.Resource(R.drawable.mobile_network_type_background, null)) + } + private fun createAndSetViewModel() { underTest = MobileIconViewModel( @@ -602,6 +651,7 @@ class MobileIconViewModelTest : SysuiTestCase() { interactor, airplaneModeInteractor, constants, + flags, testScope.backgroundScope, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt index eb6f2f81fde43c14267b024feb17f9a7c0dafc5f..f3e334ed8a22a824a04d405056df6b5bf3beb761 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt @@ -16,9 +16,12 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository @@ -41,15 +44,18 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) @SmallTest +@RunWith(AndroidJUnit4::class) class MobileIconsViewModelTest : SysuiTestCase() { private lateinit var underTest: MobileIconsViewModel private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) + private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } private lateinit var airplaneModeInteractor: AirplaneModeInteractor @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags @@ -77,6 +83,7 @@ class MobileIconsViewModelTest : SysuiTestCase() { interactor, airplaneModeInteractor, constants, + flags, testScope.backgroundScope, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt index e44ff8e212701b27970470c93cdc3b69ccc882fe..28d632d9fcea40e47457f1d9f8f92f23ba77cf3e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt @@ -41,6 +41,7 @@ class FakeConnectivityRepository : ConnectivityRepository { * setting mobile connected && validated, since the default state is disconnected && not * validated */ + @JvmOverloads fun setMobileConnected( default: Boolean = true, validated: Boolean = true, @@ -53,6 +54,7 @@ class FakeConnectivityRepository : ConnectivityRepository { } /** Similar convenience method for ethernet */ + @JvmOverloads fun setEthernetConnected( default: Boolean = true, validated: Boolean = true, @@ -64,6 +66,7 @@ class FakeConnectivityRepository : ConnectivityRepository { ) } + @JvmOverloads fun setWifiConnected( default: Boolean = true, validated: Boolean = true, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt index b4039d90681081e026b3070ad8ff01253d3ab9e3..028a58c6e1b066e393c0ef2bab77e0a3a6fef510 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt @@ -77,9 +77,10 @@ class ModernStatusBarViewTest : SysuiTestCase() { fun onDarkChanged_bindingReceivesIconAndDecorTint() { val view = createAndInitView() - view.onDarkChanged(arrayListOf(), 1.0f, 0x12345678) + view.onDarkChangedWithContrast(arrayListOf(), 0x12345678, 0x12344321) assertThat(binding.iconTint).isEqualTo(0x12345678) + assertThat(binding.contrastTint).isEqualTo(0x12344321) assertThat(binding.decorTint).isEqualTo(0x12345678) } @@ -87,9 +88,10 @@ class ModernStatusBarViewTest : SysuiTestCase() { fun setStaticDrawableColor_bindingReceivesIconTint() { val view = createAndInitView() - view.setStaticDrawableColor(0x12345678) + view.setStaticDrawableColor(0x12345678, 0x12344321) assertThat(binding.iconTint).isEqualTo(0x12345678) + assertThat(binding.contrastTint).isEqualTo(0x12344321) } @Test @@ -144,13 +146,15 @@ class ModernStatusBarViewTest : SysuiTestCase() { inner class TestBinding : ModernStatusBarViewBinding { var iconTint: Int? = null + var contrastTint: Int? = null var decorTint: Int? = null var onVisibilityStateChangedCalled: Boolean = false var shouldIconBeVisibleInternal: Boolean = true - override fun onIconTintChanged(newTint: Int) { + override fun onIconTintChanged(newTint: Int, contrastTint: Int) { iconTint = newTint + this.contrastTint = contrastTint } override fun onDecorTintChanged(newTint: Int) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt index a27f8990dec157e7f28b893661f74aaeb00fd1f6..d75a45247cbf3ad14c8b433966eacbd1484f5d7c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt @@ -25,9 +25,9 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON @@ -229,7 +229,8 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { testableLooper.processAllMessages() val color = 0x12345678 - view.onDarkChanged(arrayListOf(), 1.0f, color) + val contrast = 0x12344321 + view.onDarkChangedWithContrast(arrayListOf(), color, contrast) testableLooper.processAllMessages() assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color)) @@ -244,7 +245,8 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { testableLooper.processAllMessages() val color = 0x23456789 - view.setStaticDrawableColor(color) + val contrast = 0x12344321 + view.setStaticDrawableColor(color, contrast) testableLooper.processAllMessages() assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color)) 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 c8327029026d5e251ac633fe406276dda332bb06..123362ac57d78cd9b1fa6b5c314794e7b9c6b8df 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/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index 10ac2ebc9b2f89710512769e86da73c5c9c39f6c..f09cb19d0c1d92df7bf204b623052f09cecdf32a 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -48,3 +48,17 @@ flag { description: "Stops using the deprecated PackageListObserver." bug: "304561459" } + +flag { + name: "scan_packages_without_lock" + namespace: "accessibility" + description: "Scans packages for accessibility service/activity info without holding the A11yMS lock" + bug: "295969873" +} + +flag { + name: "reduce_touch_exploration_sensitivity" + namespace: "accessibility" + description: "Reduces touch exploration sensitivity by only sending a hover event when the ifnger has moved the amount of pixels defined by the system's touch slop." + bug: "303677860" +} diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index aa6d800510e1dbbfe0d8472600830a69952c17dc..8c1d444006bdab0078d71a9af580b4c5fa4beece 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -290,14 +290,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final Set<ComponentName> mTempComponentNameSet = new HashSet<>(); - private final List<AccessibilityServiceInfo> mTempAccessibilityServiceInfoList = - new ArrayList<>(); - private final IntArray mTempIntArray = new IntArray(0); private final RemoteCallbackList<IAccessibilityManagerClient> mGlobalClients = new RemoteCallbackList<>(); + private PackageMonitor mPackageMonitor; + @VisibleForTesting final SparseArray<AccessibilityUserState> mUserStates = new SparseArray<>(); @@ -531,6 +530,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub disableAccessibilityMenuToMigrateIfNeeded(); } + /** + * Returns if the current thread is holding {@link #mLock}. Used for testing + * deadlock bug fixes. + * + * <p><strong>Warning:</strong> this should not be used for production logic + * because by the time you receive an answer it may no longer be valid. + * </p> + */ + @VisibleForTesting + boolean unsafeIsLockHeld() { + return Thread.holdsLock(mLock); + } + @Override public int getCurrentUserIdLocked() { return mCurrentUserId; @@ -690,6 +702,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + private void onSomePackagesChangedLocked( + @Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos, + @Nullable List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) { + final AccessibilityUserState userState = getCurrentUserStateLocked(); + // Reload the installed services since some services may have different attributes + // or resolve info (does not support equals), etc. Remove them then to force reload. + userState.mInstalledServices.clear(); + if (readConfigurationForUserStateLocked(userState, + parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos)) { + onUserStateChangedLocked(userState); + } + } + private void onPackageRemovedLocked(String packageName) { final AccessibilityUserState userState = getCurrentUserState(); final Predicate<ComponentName> filter = @@ -721,8 +746,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + @VisibleForTesting + PackageMonitor getPackageMonitor() { + return mPackageMonitor; + } + private void registerBroadcastReceivers() { - PackageMonitor monitor = new PackageMonitor() { + mPackageMonitor = new PackageMonitor() { @Override public void onSomePackagesChanged() { if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) { @@ -730,13 +760,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub FLAGS_PACKAGE_BROADCAST_RECEIVER); } + final int userId = getChangingUserId(); + List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null; + List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null; + if (Flags.scanPackagesWithoutLock()) { + parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId); + parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId); + } synchronized (mLock) { // Only the profile parent can install accessibility services. // Therefore we ignore packages from linked profiles. - if (getChangingUserId() != mCurrentUserId) { + if (userId != mCurrentUserId) { return; } - onSomePackagesChangedLocked(); + if (Flags.scanPackagesWithoutLock()) { + onSomePackagesChangedLocked(parsedAccessibilityServiceInfos, + parsedAccessibilityShortcutInfos); + } else { + onSomePackagesChangedLocked(); + } } } @@ -751,8 +793,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub FLAGS_PACKAGE_BROADCAST_RECEIVER, "packageName=" + packageName + ";uid=" + uid); } + final int userId = getChangingUserId(); + List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null; + List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null; + if (Flags.scanPackagesWithoutLock()) { + parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId); + parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId); + } synchronized (mLock) { - final int userId = getChangingUserId(); if (userId != mCurrentUserId) { return; } @@ -765,8 +813,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // Reloads the installed services info to make sure the rebound service could // get a new one. userState.mInstalledServices.clear(); - final boolean configurationChanged = - readConfigurationForUserStateLocked(userState); + final boolean configurationChanged; + if (Flags.scanPackagesWithoutLock()) { + configurationChanged = readConfigurationForUserStateLocked(userState, + parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos); + } else { + configurationChanged = readConfigurationForUserStateLocked(userState); + } if (reboundAService || configurationChanged) { onUserStateChangedLocked(userState); } @@ -839,7 +892,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub }; // package changes - monitor.register(mContext, null, UserHandle.ALL, true); + mPackageMonitor.register(mContext, null, UserHandle.ALL, true); if (!Flags.deprecatePackageListObserver()) { final PackageManagerInternal pm = LocalServices.getService( @@ -1831,8 +1884,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mA11yWindowManager.onTouchInteractionEnd(); } - private void switchUser(int userId) { + @VisibleForTesting + void switchUser(int userId) { mMagnificationController.updateUserIdIfNeeded(userId); + List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null; + List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null; + if (Flags.scanPackagesWithoutLock()) { + parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId); + parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId); + } synchronized (mLock) { if (mCurrentUserId == userId && mInitialized) { return; @@ -1857,7 +1917,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mCurrentUserId = userId; AccessibilityUserState userState = getCurrentUserStateLocked(); - readConfigurationForUserStateLocked(userState); + if (Flags.scanPackagesWithoutLock()) { + readConfigurationForUserStateLocked(userState, + parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos); + } else { + readConfigurationForUserStateLocked(userState); + } mSecurityPolicy.onSwitchUserLocked(mCurrentUserId, userState.mEnabledServices); // Even if reading did not yield change, we have to update // the state since the context in which the current user @@ -2105,8 +2170,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState) { - mTempAccessibilityServiceInfoList.clear(); + /** + * Finds packages that provide AccessibilityService interfaces, and parses + * their metadata XML to build up {@link AccessibilityServiceInfo} objects. + * + * <p> + * <strong>Note:</strong> XML parsing is a resource-heavy operation that may + * stall, so this method should not be called while holding a lock. + * </p> + */ + private List<AccessibilityServiceInfo> parseAccessibilityServiceInfos(int userId) { + List<AccessibilityServiceInfo> result = new ArrayList<>(); int flags = PackageManager.GET_SERVICES | PackageManager.GET_META_DATA @@ -2114,12 +2188,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; - if (userState.getBindInstantServiceAllowedLocked()) { - flags |= PackageManager.MATCH_INSTANT; + synchronized (mLock) { + if (getUserStateLocked(userId).getBindInstantServiceAllowedLocked()) { + flags |= PackageManager.MATCH_INSTANT; + } } List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser( - new Intent(AccessibilityService.SERVICE_INTERFACE), flags, mCurrentUserId); + new Intent(AccessibilityService.SERVICE_INTERFACE), flags, userId); for (int i = 0, count = installedServices.size(); i < count; i++) { ResolveInfo resolveInfo = installedServices.get(i); @@ -2132,40 +2208,60 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub AccessibilityServiceInfo accessibilityServiceInfo; try { accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext); - if (!accessibilityServiceInfo.isWithinParcelableSize()) { - Slog.e(LOG_TAG, "Skipping service " - + accessibilityServiceInfo.getResolveInfo().getComponentInfo() - + " because service info size is larger than safe parcelable limits."); - continue; - } - if (userState.mCrashedServices.contains(serviceInfo.getComponentName())) { - // Restore the crashed attribute. - accessibilityServiceInfo.crashed = true; - } - mTempAccessibilityServiceInfoList.add(accessibilityServiceInfo); } catch (XmlPullParserException | IOException xppe) { Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", xppe); + continue; + } + if (!accessibilityServiceInfo.isWithinParcelableSize()) { + Slog.e(LOG_TAG, "Skipping service " + + accessibilityServiceInfo.getResolveInfo().getComponentInfo() + + " because service info size is larger than safe parcelable limits."); + continue; } + result.add(accessibilityServiceInfo); } + return result; + } - if (!mTempAccessibilityServiceInfoList.equals(userState.mInstalledServices)) { + private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState, + @Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos) { + for (int i = 0, count = parsedAccessibilityServiceInfos.size(); i < count; i++) { + AccessibilityServiceInfo accessibilityServiceInfo = + parsedAccessibilityServiceInfos.get(i); + if (userState.mCrashedServices.contains(accessibilityServiceInfo.getComponentName())) { + // Restore the crashed attribute. + accessibilityServiceInfo.crashed = true; + } + } + + if (!parsedAccessibilityServiceInfos.equals(userState.mInstalledServices)) { userState.mInstalledServices.clear(); - userState.mInstalledServices.addAll(mTempAccessibilityServiceInfoList); - mTempAccessibilityServiceInfoList.clear(); + userState.mInstalledServices.addAll(parsedAccessibilityServiceInfos); return true; } - - mTempAccessibilityServiceInfoList.clear(); return false; } - private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState) { - final List<AccessibilityShortcutInfo> shortcutInfos = AccessibilityManager - .getInstance(mContext).getInstalledAccessibilityShortcutListAsUser( - mContext, mCurrentUserId); - if (!shortcutInfos.equals(userState.mInstalledShortcuts)) { + /** + * Returns the {@link AccessibilityShortcutInfo}s of the installed + * accessibility shortcut targets for the given user. + * + * <p> + * <strong>Note:</strong> XML parsing is a resource-heavy operation that may + * stall, so this method should not be called while holding a lock. + * </p> + */ + private List<AccessibilityShortcutInfo> parseAccessibilityShortcutInfos(int userId) { + // TODO: b/297279151 - This should be implemented here, not by AccessibilityManager. + return AccessibilityManager.getInstance(mContext) + .getInstalledAccessibilityShortcutListAsUser(mContext, userId); + } + + private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState, + List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) { + if (!parsedAccessibilityShortcutInfos.equals(userState.mInstalledShortcuts)) { userState.mInstalledShortcuts.clear(); - userState.mInstalledShortcuts.addAll(shortcutInfos); + userState.mInstalledShortcuts.addAll(parsedAccessibilityShortcutInfos); return true; } return false; @@ -2890,9 +2986,23 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub userState.setFilterKeyEventsEnabledLocked(false); } + // ErrorProne doesn't understand that this method is only called while locked, + // returning an error for accessing mCurrentUserId. + @SuppressWarnings("GuardedBy") private boolean readConfigurationForUserStateLocked(AccessibilityUserState userState) { - boolean somethingChanged = readInstalledAccessibilityServiceLocked(userState); - somethingChanged |= readInstalledAccessibilityShortcutLocked(userState); + return readConfigurationForUserStateLocked(userState, + parseAccessibilityServiceInfos(mCurrentUserId), + parseAccessibilityShortcutInfos(mCurrentUserId)); + } + + private boolean readConfigurationForUserStateLocked( + AccessibilityUserState userState, + List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos, + List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) { + boolean somethingChanged = readInstalledAccessibilityServiceLocked( + userState, parsedAccessibilityServiceInfos); + somethingChanged |= readInstalledAccessibilityShortcutLocked( + userState, parsedAccessibilityShortcutInfos); somethingChanged |= readEnabledAccessibilityServicesLocked(userState); somethingChanged |= readTouchExplorationGrantedAccessibilityServicesLocked(userState); somethingChanged |= readTouchExplorationEnabledSettingLocked(userState); diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index c4184854e690201ea9515fa26161140fb67cc141..fc8d4f89e6a7c56d696c8f88227d7d3ca34161bd 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -882,10 +882,22 @@ public class TouchExplorer extends BaseEventStreamTransformation final int pointerIndex = event.findPointerIndex(pointerId); switch (event.getPointerCount()) { case 1: - // Touch exploration. + // Touch exploration. sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags); - mDispatcher.sendMotionEvent( - event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags); + if (Flags.reduceTouchExplorationSensitivity() + && mState.getLastInjectedHoverEvent() != null) { + final MotionEvent lastEvent = mState.getLastInjectedHoverEvent(); + final float deltaX = lastEvent.getX() - rawEvent.getX(); + final float deltaY = lastEvent.getY() - rawEvent.getY(); + final double moveDelta = Math.hypot(deltaX, deltaY); + if (moveDelta > mTouchSlop) { + mDispatcher.sendMotionEvent( + event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags); + } + } else { + mDispatcher.sendMotionEvent( + event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags); + } break; case 2: if (mGestureDetector.isMultiFingerGesturesEnabled() diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 72242d265e798fdfa5938646c28c0b4e62cdb78f..e4f1d3acce6df0b78031abcdc2b51dd5533af7df 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -363,6 +363,7 @@ public final class AutofillManagerService case AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS: case AutofillFeatureFlags.DEVICE_CONFIG_PREFER_PROVIDER_OVER_PCC: case AutofillFeatureFlags.DEVICE_CONFIG_PCC_USE_FALLBACK: + case Flags.FLAG_AUTOFILL_CREDMAN_INTEGRATION: setDeviceConfigProperties(); break; case AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES: diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 8a2aa616f8e6197597700cf3fdd7a158e6ac28d1..f5562d27f4e82951f47234754dfd7e1696a8df3e 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -122,9 +122,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private static final String TAG = "VirtualDeviceImpl"; /** - * Virtual displays created by a {@link VirtualDeviceManager.VirtualDevice} are more consistent - * with virtual displays created via {@link DisplayManager} and allow for the creation of - * private, auto-mirror, and fixed orientation displays since + * Virtual displays created by a {@code VirtualDeviceManager.VirtualDevice} are more consistent + * with virtual displays created via {@link android.hardware.display.DisplayManager} and allow + * for the creation of private, auto-mirror, and fixed orientation displays since * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}. * * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index 1a8dd3a7316efef155c6cb8a4872031a8c827c5d..ee41a69c6f4cad177158f645252d792626e7b175 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -28,6 +28,7 @@ import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_OK; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_SECURITY_EXCEPTION; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_TRUE; import static android.view.contentcapture.ContentCaptureSession.STATE_DISABLED; +import static android.view.contentprotection.flags.Flags.parseGroupsConfigEnabled; import static com.android.internal.util.FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ACCEPT_DATA_SHARE_REQUEST; import static com.android.internal.util.FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_CLIENT_PIPE_FAIL; @@ -115,6 +116,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -142,6 +144,9 @@ public class ContentCaptureManagerService extends private static final int MAX_CONCURRENT_FILE_SHARING_REQUESTS = 10; private static final int DATA_SHARE_BYTE_BUFFER_LENGTH = 1_024; + private static final String CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_GROUP = ";"; + private static final String CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_VALUE = ","; + // Needed to pass checkstyle_hook as names are too long for one line. private static final int EVENT__DATA_SHARE_ERROR_CONCURRENT_REQUEST = CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_CONCURRENT_REQUEST; @@ -957,14 +962,40 @@ public class ContentCaptureManagerService extends return mContentCaptureManagerServiceStub; } - /** @hide */ + /** + * Parses a simple config in format "group;group" where each "group" is itself in the format of + * "string1,string2", eg: + * + * <p>"a" -> [["a"]] + * + * <p>"a,b" -> [["a", "b"]] + * + * <p>"a,b;c;d,e" -> [["a", "b"], ["c"], ["d", "e"]] + * + * @hide + */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @NonNull protected List<List<String>> parseContentProtectionGroupsConfig(@Nullable String config) { if (verbose) { Slog.v(TAG, "parseContentProtectionGroupsConfig: " + config); } - return Collections.emptyList(); + if (!parseGroupsConfigEnabled()) { + return Collections.emptyList(); + } + if (config == null) { + return Collections.emptyList(); + } + return Arrays.stream(config.split(CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_GROUP)) + .map(this::parseContentProtectionGroupConfigValues) + .filter(group -> !group.isEmpty()) + .toList(); + } + + private List<String> parseContentProtectionGroupConfigValues(@NonNull String group) { + return Arrays.stream(group.split(CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_VALUE)) + .filter(value -> !value.isEmpty()) + .toList(); } final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub { diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 962f38f10b5de1968c95be9caba9c705ef32ec69..beea063221fb197625e7388a5dbca80632f0550f 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -214,9 +214,12 @@ class StorageManagerService extends IStorageManager.Stub // external storage service. public static final int FAILED_MOUNT_RESET_TIMEOUT_SECONDS = 10; - /** Extended timeout for the system server watchdog. */ + /** Extended timeout for the system server watchdog. */ private static final int SLOW_OPERATION_WATCHDOG_TIMEOUT_MS = 60 * 1000; + /** Extended timeout for the system server watchdog for vold#partition operation. */ + private static final int PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS = 3 * 60 * 1000; + @GuardedBy("mLock") private final Set<Integer> mFuseMountedUser = new ArraySet<>(); @@ -2338,6 +2341,8 @@ class StorageManagerService extends IStorageManager.Stub try { // TODO(b/135341433): Remove cautious logging when FUSE is stable Slog.i(TAG, "Mounting volume " + vol); + Watchdog.getInstance().setOneOffTimeoutForMonitors( + SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#mount might be slow"); mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() { @Override public boolean onVolumeChecking(FileDescriptor fd, String path, @@ -2463,10 +2468,12 @@ class StorageManagerService extends IStorageManager.Stub @android.annotation.EnforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS) @Override public void partitionPublic(String diskId) { - super.partitionPublic_enforcePermission(); final CountDownLatch latch = findOrCreateDiskScanLatch(diskId); + + Watchdog.getInstance().setOneOffTimeoutForMonitors( + PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow"); try { mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1); waitForLatch(latch, "partitionPublic", 3 * DateUtils.MINUTE_IN_MILLIS); @@ -2483,6 +2490,9 @@ class StorageManagerService extends IStorageManager.Stub enforceAdminUser(); final CountDownLatch latch = findOrCreateDiskScanLatch(diskId); + + Watchdog.getInstance().setOneOffTimeoutForMonitors( + PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow"); try { mVold.partition(diskId, IVold.PARTITION_TYPE_PRIVATE, -1); waitForLatch(latch, "partitionPrivate", 3 * DateUtils.MINUTE_IN_MILLIS); @@ -2499,6 +2509,9 @@ class StorageManagerService extends IStorageManager.Stub enforceAdminUser(); final CountDownLatch latch = findOrCreateDiskScanLatch(diskId); + + Watchdog.getInstance().setOneOffTimeoutForMonitors( + PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow"); try { mVold.partition(diskId, IVold.PARTITION_TYPE_MIXED, ratio); waitForLatch(latch, "partitionMixed", 3 * DateUtils.MINUTE_IN_MILLIS); diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 553b08501925893524128747b47b88a91a837941..0956c6ded01388a9f499b6014fec0580e433ccc9 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -409,6 +409,13 @@ public final class ActiveServices { AppWidgetManagerInternal mAppWidgetManagerInternal; + /** + * The available ANR timers. + */ + private final ProcessAnrTimer mActiveServiceAnrTimer; + private final ServiceAnrTimer mShortFGSAnrTimer; + private final ServiceAnrTimer mServiceFGAnrTimer; + // allowlisted packageName. ArraySet<String> mAllowListWhileInUsePermissionInFgs = new ArraySet<>(); @@ -663,6 +670,15 @@ public final class ActiveServices { final IBinder b = ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE); this.mFGSLogger = new ForegroundServiceTypeLoggerModule(); + this.mActiveServiceAnrTimer = new ProcessAnrTimer(service, + ActivityManagerService.SERVICE_TIMEOUT_MSG, + "SERVICE_TIMEOUT"); + this.mShortFGSAnrTimer = new ServiceAnrTimer(service, + ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, + "FGS_TIMEOUT"); + this.mServiceFGAnrTimer = new ServiceAnrTimer(service, + ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, + "SERVICE_FOREGROUND_TIMEOUT"); } void systemServicesReady() { @@ -2083,8 +2099,7 @@ public final class ActiveServices { r.fgRequired = false; r.fgWaiting = false; alreadyStartedOp = stopProcStatsOp = true; - mAm.mHandler.removeMessages( - ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r); + mServiceFGAnrTimer.cancel(r); } final ProcessServiceRecord psr = r.app.mServices; @@ -3313,7 +3328,7 @@ public final class ActiveServices { } void unscheduleShortFgsTimeoutLocked(ServiceRecord sr) { - mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr); + mShortFGSAnrTimer.cancel(sr); mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_PROCSTATE_TIMEOUT_MSG, sr); mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_TIMEOUT_MSG, sr); @@ -3387,9 +3402,11 @@ public final class ActiveServices { Slog.d(TAG_SERVICE, "[STALE] Short FGS timed out: " + sr + " " + sr.getShortFgsTimedEventDescription(nowUptime)); } + mShortFGSAnrTimer.discard(sr); return; } Slog.e(TAG_SERVICE, "Short FGS timed out: " + sr); + mShortFGSAnrTimer.accept(sr); traceInstant("short FGS timeout: ", sr); logFGSStateChangeLocked(sr, @@ -3413,11 +3430,10 @@ public final class ActiveServices { msg, sr.getShortFgsInfo().getProcStateDemoteTime()); } - { - final Message msg = mAm.mHandler.obtainMessage( - ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr); - mAm.mHandler.sendMessageAtTime(msg, sr.getShortFgsInfo().getAnrTime()); - } + // ServiceRecord.getAnrTime() is an absolute time with a reference that is not "now". + // Compute the time from "now" when starting the anr timer. + mShortFGSAnrTimer.start(sr, + sr.getShortFgsInfo().getAnrTime() - SystemClock.uptimeMillis()); } } @@ -4847,8 +4863,7 @@ public final class ActiveServices { // a new SERVICE_FOREGROUND_TIMEOUT_MSG is scheduled in SERVICE_START_FOREGROUND_TIMEOUT // again. if (r.fgRequired && r.fgWaiting) { - mAm.mHandler.removeMessages( - ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r); + mServiceFGAnrTimer.cancel(r); r.fgWaiting = false; } @@ -5691,8 +5706,7 @@ public final class ActiveServices { } mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService), AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null); - mAm.mHandler.removeMessages( - ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r); + mServiceFGAnrTimer.cancel(r); if (r.app != null) { Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG); @@ -6128,7 +6142,7 @@ public final class ActiveServices { if (psr.numberOfExecutingServices() == 0) { if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, "No more executingServices of " + r.shortInstanceName); - mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app); + if (r.app.mPid != 0) mActiveServiceAnrTimer.cancel(r.app); } else if (r.executeFg) { // Need to re-evaluate whether the app still needs to be in the foreground. for (int i = psr.numberOfExecutingServices() - 1; i >= 0; i--) { @@ -6816,13 +6830,16 @@ public final class ActiveServices { synchronized (mAm) { if (proc.isDebugging()) { // The app's being debugged, ignore timeout. + mActiveServiceAnrTimer.discard(proc); return; } final ProcessServiceRecord psr = proc.mServices; if (psr.numberOfExecutingServices() == 0 || proc.getThread() == null || proc.isKilled()) { + mActiveServiceAnrTimer.discard(proc); return; } + mActiveServiceAnrTimer.accept(proc); final long now = SystemClock.uptimeMillis(); final long maxTime = now - (psr.shouldExecServicesFg() @@ -6855,12 +6872,11 @@ public final class ActiveServices { timeoutRecord = TimeoutRecord.forServiceExec(timeout.shortInstanceName, waitedMillis); } else { - Message msg = mAm.mHandler.obtainMessage( - ActivityManagerService.SERVICE_TIMEOUT_MSG); - msg.obj = proc; - mAm.mHandler.sendMessageAtTime(msg, psr.shouldExecServicesFg() - ? (nextTime + mAm.mConstants.SERVICE_TIMEOUT) : - (nextTime + mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT)); + final long delay = psr.shouldExecServicesFg() + ? (nextTime + mAm.mConstants.SERVICE_TIMEOUT) : + (nextTime + mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT) + - SystemClock.uptimeMillis(); + mActiveServiceAnrTimer.start(proc, delay); } } @@ -6886,12 +6902,15 @@ public final class ActiveServices { synchronized (mAm) { timeoutRecord.mLatencyTracker.waitingOnAMSLockEnded(); if (!r.fgRequired || !r.fgWaiting || r.destroying) { + mServiceFGAnrTimer.discard(r); return; } + mServiceFGAnrTimer.accept(r); app = r.app; if (app != null && app.isDebugging()) { // The app's being debugged; let it ride + mServiceFGAnrTimer.discard(r); return; } @@ -6948,26 +6967,46 @@ public final class ActiveServices { ForegroundServiceDidNotStartInTimeException.createExtrasForService(service)); } + private static class ProcessAnrTimer extends AnrTimer<ProcessRecord> { + + ProcessAnrTimer(ActivityManagerService am, int msg, String label) { + super(Objects.requireNonNull(am).mHandler, msg, label); + } + + void start(@NonNull ProcessRecord proc, long millis) { + start(proc, proc.getPid(), proc.uid, millis); + } + } + + private static class ServiceAnrTimer extends AnrTimer<ServiceRecord> { + + ServiceAnrTimer(ActivityManagerService am, int msg, String label) { + super(Objects.requireNonNull(am).mHandler, msg, label); + } + + void start(@NonNull ServiceRecord service, long millis) { + start(service, + (service.app != null) ? service.app.getPid() : 0, + service.appInfo.uid, + millis); + } + } + void scheduleServiceTimeoutLocked(ProcessRecord proc) { if (proc.mServices.numberOfExecutingServices() == 0 || proc.getThread() == null) { return; } - Message msg = mAm.mHandler.obtainMessage( - ActivityManagerService.SERVICE_TIMEOUT_MSG); - msg.obj = proc; - mAm.mHandler.sendMessageDelayed(msg, proc.mServices.shouldExecServicesFg() - ? mAm.mConstants.SERVICE_TIMEOUT : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT); + final long delay = proc.mServices.shouldExecServicesFg() + ? mAm.mConstants.SERVICE_TIMEOUT : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT; + mActiveServiceAnrTimer.start(proc, delay); } void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) { if (r.app.mServices.numberOfExecutingServices() == 0 || r.app.getThread() == null) { return; } - Message msg = mAm.mHandler.obtainMessage( - ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG); - msg.obj = r; r.fgWaiting = true; - mAm.mHandler.sendMessageDelayed(msg, mAm.mConstants.mServiceStartForegroundTimeoutMs); + mServiceFGAnrTimer.start(r, mAm.mConstants.mServiceStartForegroundTimeoutMs); } final class ServiceDumper { diff --git a/services/core/java/com/android/server/am/AnrTimer.java b/services/core/java/com/android/server/am/AnrTimer.java index 9ba49ce35dad2996a9471c77082863f368e248c0..3e17930e3cb9db0ef7e63d85397e36b6b5ecc19a 100644 --- a/services/core/java/com/android/server/am/AnrTimer.java +++ b/services/core/java/com/android/server/am/AnrTimer.java @@ -28,6 +28,7 @@ import android.os.Process; import android.os.SystemClock; import android.os.Trace; import android.text.TextUtils; +import android.text.format.TimeMigrationUtils; import android.util.ArrayMap; import android.util.IndentingPrintWriter; import android.util.Log; @@ -44,7 +45,6 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; -import java.util.Date; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; @@ -150,7 +150,7 @@ class AnrTimer<V> { /** A partial stack that localizes the caller of the operation. */ final StackTraceElement[] stack; /** The date, in local time, the error was created. */ - final String date; + final long timestamp; Error(@NonNull String issue, @NonNull String operation, @NonNull String tag, @NonNull StackTraceElement[] stack, @NonNull String arg) { @@ -159,7 +159,7 @@ class AnrTimer<V> { this.tag = tag; this.stack = stack; this.arg = arg; - this.date = new Date().toString(); + this.timestamp = SystemClock.elapsedRealtime(); } } @@ -347,20 +347,23 @@ class AnrTimer<V> { * main Looper. */ @NonNull - Handler getHandler(@NonNull Handler.Callback callback) { + Handler newHandler(@NonNull Handler.Callback callback) { Looper looper = mReferenceHandler.getLooper(); if (looper == null) looper = Looper.getMainLooper(); return new Handler(looper, callback); - }; + } - /** Return a CpuTracker. */ + /** + * Return a CpuTracker. The default behavior is to create a new CpuTracker but this changes + * for unit tests. + **/ @NonNull - CpuTracker getTracker() { + CpuTracker newTracker() { return new CpuTracker(); } /** Return true if the feature is enabled. */ - boolean getFeatureEnabled() { + boolean isFeatureEnabled() { return anrTimerServiceEnabled(); } } @@ -401,8 +404,8 @@ class AnrTimer<V> { /** Create a HandlerTimerService that directly uses the supplied handler and tracker. */ @VisibleForTesting HandlerTimerService(@NonNull Injector injector) { - mHandler = injector.getHandler(this::expires); - mCpu = injector.getTracker(); + mHandler = injector.newHandler(this::expires); + mCpu = injector.newTracker(); } /** Post a message with the specified timeout. The timer is not modified. */ @@ -513,7 +516,26 @@ class AnrTimer<V> { private final FeatureSwitch mFeature; /** - * The common constructor. A null injector results in a normal, production timer. + * Create one AnrTimer instance. The instance is given a handler and a "what". Individual + * timers are started with {@link #start}. If a timer expires, then a {@link Message} is sent + * immediately to the handler with {@link Message.what} set to what and {@link Message.obj} set + * to the timer key. + * + * AnrTimer instances have a label, which must be unique. The label is used for reporting and + * debug. + * + * If an individual timer expires internally, and the "extend" parameter is true, then the + * AnrTimer may extend the individual timer rather than immediately delivering the timeout to + * the client. The extension policy is not part of the instance. + * + * This method accepts an {@link #Injector} to tune behavior for testing. This method should + * not be called directly by regular clients. + * + * @param handler The handler to which the expiration message will be delivered. + * @param what The "what" parameter for the expiration message. + * @param label A name for this instance. + * @param extend A flag to indicate if expired timers can be granted extensions. + * @param injector An {@link #Injector} to tune behavior for testing. */ @VisibleForTesting AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend, @@ -522,7 +544,7 @@ class AnrTimer<V> { mWhat = what; mLabel = label; mExtend = extend; - boolean enabled = injector.getFeatureEnabled(); + boolean enabled = injector.isFeatureEnabled(); if (!enabled) { mFeature = new FeatureDisabled(); mTimerService = null; @@ -538,14 +560,25 @@ class AnrTimer<V> { } /** - * Create one timer instance for production. The client can ask for extensible timeouts. + * Create an AnrTimer instance with the default {@link #Injector}. See {@link AnrTimer(Handler, + * int, String, boolean, Injector} for a functional description. + * + * @param handler The handler to which the expiration message will be delivered. + * @param what The "what" parameter for the expiration message. + * @param label A name for this instance. + * @param extend A flag to indicate if expired timers can be granted extensions. */ AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) { this(handler, what, label, extend, new Injector(handler)); } /** - * Create one timer instance for production. There are no extensible timeouts. + * Create an AnrTimer instance with the default {@link #Injector} and with extensions disabled. + * See {@link AnrTimer(Handler, int, String, boolean, Injector} for a functional description. + * + * @param handler The handler to which the expiration message will be delivered. + * @param what The "what" parameter for the expiration message. + * @param label A name for this instance. */ AnrTimer(@NonNull Handler handler, int what, @NonNull String label) { this(handler, what, label, false); @@ -555,6 +588,8 @@ class AnrTimer<V> { * Return true if the service is enabled on this instance. Clients should use this method to * decide if the feature is enabled, and not read the flags directly. This method should be * deleted if and when the feature is enabled permanently. + * + * @return true if the service is flag-enabled. */ boolean serviceEnabled() { return mFeature.enabled(); @@ -642,7 +677,7 @@ class AnrTimer<V> { } /** - * Report something about a timer. + * Generate a log message for a timer. */ private void report(@NonNull Timer timer, @NonNull String msg) { Log.i(TAG, msg + " " + timer + " " + Objects.toString(timer.arg)); @@ -654,9 +689,13 @@ class AnrTimer<V> { */ private abstract class FeatureSwitch { abstract boolean start(@NonNull V arg, int pid, int uid, long timeoutMs); + abstract boolean cancel(@NonNull V arg); + abstract boolean accept(@NonNull V arg); + abstract boolean discard(@NonNull V arg); + abstract boolean enabled(); } @@ -666,6 +705,7 @@ class AnrTimer<V> { */ private class FeatureDisabled extends FeatureSwitch { /** Start a timer by sending a message to the client's handler. */ + @Override boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) { final Message msg = mHandler.obtainMessage(mWhat, arg); mHandler.sendMessageDelayed(msg, timeoutMs); @@ -673,22 +713,26 @@ class AnrTimer<V> { } /** Cancel a timer by removing the message from the client's handler. */ + @Override boolean cancel(@NonNull V arg) { mHandler.removeMessages(mWhat, arg); return true; } /** accept() is a no-op when the feature is disabled. */ + @Override boolean accept(@NonNull V arg) { return true; } /** discard() is a no-op when the feature is disabled. */ + @Override boolean discard(@NonNull V arg) { return true; } /** The feature is not enabled. */ + @Override boolean enabled() { return false; } @@ -703,16 +747,17 @@ class AnrTimer<V> { /** * Start a timer. */ + @Override boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) { final Timer timer = Timer.obtain(pid, uid, arg, timeoutMs, AnrTimer.this); synchronized (mLock) { Timer old = mTimerMap.get(arg); + // There is an existing timer. If the timer was running, then cancel the running + // timer and restart it. If the timer was expired record a protocol error and + // discard the expired timer. if (old != null) { - // There is an existing timer. This is a protocol error in the client. - // Record the error and then clean up by canceling running timers and - // discarding expired timers. - restartedLocked(old.status, arg); if (old.status == TIMER_EXPIRED) { + restartedLocked(old.status, arg); discard(arg); } else { cancel(arg); @@ -735,6 +780,7 @@ class AnrTimer<V> { /** * Cancel a timer. Return false if the timer was not found. */ + @Override boolean cancel(@NonNull V arg) { synchronized (mLock) { Timer timer = removeLocked(arg); @@ -755,6 +801,7 @@ class AnrTimer<V> { * Accept a timer in the framework-level handler. The timeout has been accepted and the * timeout handler is executing. Return false if the timer was not found. */ + @Override boolean accept(@NonNull V arg) { synchronized (mLock) { Timer timer = removeLocked(arg); @@ -775,6 +822,7 @@ class AnrTimer<V> { * longer interesting. No statistics are collected. Return false if the time was not * found. */ + @Override boolean discard(@NonNull V arg) { synchronized (mLock) { Timer timer = removeLocked(arg); @@ -791,40 +839,58 @@ class AnrTimer<V> { } /** The feature is enabled. */ + @Override boolean enabled() { return true; } } /** - * Start a timer associated with arg. If a timer already exists with the same arg, then that - * timer is canceled and a new timer is created. This returns false if the timer cannot be - * created. + * Start a timer associated with arg. The same object must be used to cancel, accept, or + * discard a timer later. If a timer already exists with the same arg, then the existing timer + * is canceled and a new timer is created. + * + * @param arg The key by which the timer is known. This is never examined or modified. + * @param pid The Linux process ID of the target being timed. + * @param uid The Linux user ID of the target being timed. + * @param timeoutMs The timer timeout, in milliseconds. + * @return true if the timer was successfully created. */ boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) { return mFeature.start(arg, pid, uid, timeoutMs); } /** - * Cancel a running timer and remove it from any list. This returns true if the timer was - * found and false otherwise. It is not an error to cancel a non-existent timer. It is also - * not an error to cancel an expired timer. + * Cancel the running timer associated with arg. The timer is forgotten. If the timer has + * expired, the call is treated as a discard. No errors are reported if the timer does not + * exist or if the timer has expired. + * + * @return true if the timer was found and was running. */ boolean cancel(@NonNull V arg) { return mFeature.cancel(arg); } /** - * Accept an expired timer. This returns false if the timer was not found or if the timer was - * not expired. + * Accept the expired timer associated with arg. This indicates that the caller considers the + * timer expiration to be a true ANR. (See {@link #discard} for an alternate response.) It is + * an error to accept a running timer, however the running timer will be canceled. + * + * @return true if the timer was found and was expired. */ boolean accept(@NonNull V arg) { return mFeature.accept(arg); } /** - * Discard an expired timer. This returns false if the timer was not found or if the timer was - * not expired. + * Discard the expired timer associated with arg. This indicates that the caller considers the + * timer expiration to be a false ANR. ((See {@link #accept} for an alternate response.) One + * reason to discard an expired timer is if the process being timed was also being debugged: + * such a process could be stopped at a breakpoint and its failure to respond would not be an + * error. It is an error to discard a running timer, however the running timer will be + * canceled. + * + * @return true if the timer was found and was expired. */ boolean discard(@NonNull V arg) { return mFeature.discard(arg); @@ -913,7 +979,10 @@ class AnrTimer<V> { private static void dump(IndentingPrintWriter ipw, int seq, Error err) { ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, err.operation, err.tag, err.issue, err.arg); - ipw.format(" date:%s\n", err.date); + + final long offset = System.currentTimeMillis() - SystemClock.elapsedRealtime(); + final long etime = offset + err.timestamp; + ipw.println(" date:" + TimeMigrationUtils.formatMillisWithFixedFormat(etime)); ipw.increaseIndent(); for (int i = 0; i < err.stack.length; i++) { ipw.println(" " + err.stack[i].toString()); diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index a428907073680b5a2cf65b2b0aa590488149f88c..d19eae5b0709933c930e41eb35c8c9466065b9a6 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -258,7 +258,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private static final int MSG_PROCESS_FREEZABLE_CHANGED = 6; private static final int MSG_UID_STATE_CHANGED = 7; - // Required when Flags.anrTimerServiceEnabled is false. + // Required when Flags.anrTimerServiceEnabled is false. This constant should be deleted if and + // when the flag is fused on. private static final int MSG_DELIVERY_TIMEOUT_SOFT = 8; private void enqueueUpdateRunningList() { @@ -274,7 +275,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { updateRunningList(); return true; } - // Required when Flags.anrTimerServiceEnabled is false. + // Required when Flags.anrTimerServiceEnabled is false. This case should be deleted if + // and when the flag is fused on. case MSG_DELIVERY_TIMEOUT_SOFT: { synchronized (mService) { deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj, msg.arg1); @@ -1169,7 +1171,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { r.resultTo = null; } - // Required when Flags.anrTimerServiceEnabled is false. + // Required when Flags.anrTimerServiceEnabled is false. This function can be replaced with a + // single call to {@code mAnrTimer.start()} if and when the flag is fused on. private void startDeliveryTimeoutLocked(@NonNull BroadcastProcessQueue queue, int softTimeoutMillis) { if (mAnrTimer.serviceEnabled()) { @@ -1181,7 +1184,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } - // Required when Flags.anrTimerServiceEnabled is false. + // Required when Flags.anrTimerServiceEnabled is false. This function can be replaced with a + // single call to {@code mAnrTimer.cancel()} if and when the flag is fused on. private void cancelDeliveryTimeoutLocked(@NonNull BroadcastProcessQueue queue) { mAnrTimer.cancel(queue); if (!mAnrTimer.serviceEnabled()) { @@ -1189,7 +1193,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } - // Required when Flags.anrTimerServiceEnabled is false. + // Required when Flags.anrTimerServiceEnabled is false. This function can be deleted entirely + // if and when the flag is fused on. private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue, int softTimeoutMillis) { if (queue.app != null) { diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 1ba1f55af71bc0bd97cafc0db18b84b5ef1da6a0..16e3fdf2a6abae4c4c37e6801a54422b9022fc38 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) { @@ -331,6 +350,35 @@ public class SettingsToPropertiesMapper { return propertyName; } + /** + * 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]". diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java index 6c5f3e74b0d2dfd415d5ef2d21c65c6ef3c64ba6..d65c7c2c526d535b1ccc0927adc63dd7d15f3097 100644 --- a/services/core/java/com/android/server/audio/SoundDoseHelper.java +++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java @@ -189,6 +189,8 @@ public class SoundDoseHelper { private final AtomicBoolean mEnableCsd = new AtomicBoolean(false); + private final AtomicBoolean mForceCsdProperty = new AtomicBoolean(false); + private final Object mCsdAsAFeatureLock = new Object(); @GuardedBy("mCsdAsAFeatureLock") @@ -375,9 +377,21 @@ public class SoundDoseHelper { } } + private boolean updateCsdForTestApi() { + if (mForceCsdProperty.get() != SystemProperties.getBoolean( + SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE, false)) { + updateCsdEnabled("SystemPropertiesChangeCallback"); + } + + return mEnableCsd.get(); + } + float getCsd() { if (!mEnableCsd.get()) { - return -1.f; + // since this will only be called by a test api enable csd if system property is set + if (!updateCsdForTestApi()) { + return -1.f; + } } final ISoundDose soundDose = mSoundDose.get(); @@ -396,7 +410,10 @@ public class SoundDoseHelper { void setCsd(float csd) { if (!mEnableCsd.get()) { - return; + // since this will only be called by a test api enable csd if system property is set + if (!updateCsdForTestApi()) { + return; + } } SoundDoseRecord[] doseRecordsArray; @@ -430,7 +447,10 @@ public class SoundDoseHelper { void resetCsdTimeouts() { if (!mEnableCsd.get()) { - return; + // since this will only be called by a test api enable csd if system property is set + if (!updateCsdForTestApi()) { + return; + } } synchronized (mCsdStateLock) { @@ -440,7 +460,10 @@ public class SoundDoseHelper { void forceUseFrameworkMel(boolean useFrameworkMel) { if (!mEnableCsd.get()) { - return; + // since this will only be called by a test api enable csd if system property is set + if (!updateCsdForTestApi()) { + return; + } } final ISoundDose soundDose = mSoundDose.get(); @@ -458,7 +481,10 @@ public class SoundDoseHelper { void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices) { if (!mEnableCsd.get()) { - return; + // since this will only be called by a test api enable csd if system property is set + if (!updateCsdForTestApi()) { + return; + } } final ISoundDose soundDose = mSoundDose.get(); @@ -488,7 +514,7 @@ public class SoundDoseHelper { try { return soundDose.isSoundDoseHalSupported(); } catch (RemoteException e) { - Log.e(TAG, "Exception while forcing CSD computation on all devices", e); + Log.e(TAG, "Exception while querying the csd enabled status", e); } return false; } @@ -544,7 +570,7 @@ public class SoundDoseHelper { audioDeviceCategory.csdCompatible = isHeadphone; soundDose.setAudioDeviceCategory(audioDeviceCategory); } catch (RemoteException e) { - Log.e(TAG, "Exception while forcing the internal MEL computation", e); + Log.e(TAG, "Exception while setting the audio device category", e); } } @@ -894,7 +920,7 @@ public class SoundDoseHelper { mCachedAudioDeviceCategories.clear(); } } catch (RemoteException e) { - Log.e(TAG, "Exception while forcing the internal MEL computation", e); + Log.e(TAG, "Exception while initializing the cached audio device categories", e); } synchronized (mCsdAsAFeatureLock) { @@ -991,19 +1017,20 @@ public class SoundDoseHelper { } private void updateCsdEnabled(String caller) { - boolean csdForce = SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE, false); + mForceCsdProperty.set(SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE, + false)); // we are using the MCC overlaid legacy flag used for the safe volume enablement // to determine whether the MCC enforces any safe hearing standard. boolean mccEnforcedSafeMedia = mContext.getResources().getBoolean( com.android.internal.R.bool.config_safe_media_volume_enabled); boolean csdEnable = mContext.getResources().getBoolean( R.bool.config_safe_sound_dosage_enabled); - boolean newEnabledCsd = (mccEnforcedSafeMedia && csdEnable) || csdForce; + boolean newEnabledCsd = (mccEnforcedSafeMedia && csdEnable) || mForceCsdProperty.get(); synchronized (mCsdAsAFeatureLock) { if (!mccEnforcedSafeMedia && csdEnable) { mIsCsdAsAFeatureAvailable = true; - newEnabledCsd = mIsCsdAsAFeatureEnabled || csdForce; + newEnabledCsd = mIsCsdAsAFeatureEnabled || mForceCsdProperty.get(); Log.v(TAG, caller + ": CSD as a feature is not enforced and enabled: " + newEnabledCsd); } else { diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index 4538cad513d621fff861c8e2e708d94cb5a80379..1760bb3c7a6a9bc4e1856c1e0d4319b9ff9f9423 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -41,6 +41,7 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.IAuthService; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.biometrics.IBiometricPromptStatusListener; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IInvalidationCallback; @@ -356,6 +357,18 @@ public class AuthService extends SystemService { } } + @Override + public void registerBiometricPromptStatusListener( + IBiometricPromptStatusListener listener) throws RemoteException { + checkInternalPermission(); + final long identity = Binder.clearCallingIdentity(); + try { + mBiometricService.registerBiometricPromptStatusListener(listener); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + @Override public void invalidateAuthenticatorIds(int userId, int fromSensorId, IInvalidationCallback callback) throws RemoteException { diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 1898b8015462faab658ed8ede878d22c702ad11a..9569f23e8d4998e0c62cc30978ebeced7d52d41e 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -41,6 +41,7 @@ import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.biometrics.IBiometricPromptStatusListener; import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; @@ -88,6 +89,7 @@ import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Supplier; @@ -105,6 +107,8 @@ public class BiometricService extends SystemService { @VisibleForTesting final SettingObserver mSettingObserver; private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks; + private final ConcurrentLinkedQueue<BiometricPromptStatusListener> + mBiometricPromptStatusListeners; private final Random mRandom = new Random(); @NonNull private final Supplier<Long> mRequestCounter; @NonNull private final BiometricContext mBiometricContext; @@ -425,6 +429,42 @@ public class BiometricService extends SystemService { } } + final class BiometricPromptStatusListener implements IBinder.DeathRecipient { + private final IBiometricPromptStatusListener mBiometricPromptStatusListener; + + BiometricPromptStatusListener(IBiometricPromptStatusListener callback) { + mBiometricPromptStatusListener = callback; + } + + void notifyBiometricPromptShowing() { + try { + mBiometricPromptStatusListener.onBiometricPromptShowing(); + } catch (DeadObjectException e) { + Slog.w(TAG, "Death while invoking notifyHandleAuthenticate", e); + mBiometricPromptStatusListeners.remove(this); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to invoke notifyHandleAuthenticate", e); + } + } + + void notifyBiometricPromptIdle() { + try { + mBiometricPromptStatusListener.onBiometricPromptIdle(); + } catch (DeadObjectException e) { + Slog.w(TAG, "Death while invoking notifyDialogDismissed", e); + mBiometricPromptStatusListeners.remove(this); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to invoke notifyDialogDismissed", e); + } + } + + @Override + public void binderDied() { + Slog.e(TAG, "Biometric prompt callback binder died"); + mBiometricPromptStatusListeners.remove(this); + } + } + // Receives events from individual biometric sensors. private IBiometricSensorReceiver createBiometricSensorReceiver(final long requestId) { return new IBiometricSensorReceiver.Stub() { @@ -703,6 +743,22 @@ public class BiometricService extends SystemService { } } + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) + @Override // Binder call + public void registerBiometricPromptStatusListener(IBiometricPromptStatusListener callback) { + super.registerBiometricPromptStatusListener_enforcePermission(); + + BiometricPromptStatusListener biometricPromptStatusListener = + new BiometricPromptStatusListener(callback); + mBiometricPromptStatusListeners.add(biometricPromptStatusListener); + + if (mAuthSession != null) { + biometricPromptStatusListener.notifyBiometricPromptShowing(); + } else { + biometricPromptStatusListener.notifyBiometricPromptIdle(); + } + } + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call public void invalidateAuthenticatorIds(int userId, int fromSensorId, @@ -1044,6 +1100,7 @@ public class BiometricService extends SystemService { mDevicePolicyManager = mInjector.getDevicePolicyManager(context); mImpl = new BiometricServiceWrapper(); mEnabledOnKeyguardCallbacks = new ArrayList<>(); + mBiometricPromptStatusListeners = new ConcurrentLinkedQueue<>(); mSettingObserver = mInjector.getSettingObserver(context, mHandler, mEnabledOnKeyguardCallbacks); mRequestCounter = mInjector.getRequestGenerator(); @@ -1158,6 +1215,7 @@ public class BiometricService extends SystemService { if (finished) { Slog.d(TAG, "handleOnError: AuthSession finished"); mAuthSession = null; + notifyAuthSessionChanged(); } } catch (RemoteException e) { Slog.e(TAG, "RemoteException", e); @@ -1186,6 +1244,7 @@ public class BiometricService extends SystemService { session.onDialogDismissed(reason, credentialAttestation); mAuthSession = null; + notifyAuthSessionChanged(); } private void handleOnTryAgainPressed(long requestId) { @@ -1235,6 +1294,7 @@ public class BiometricService extends SystemService { final boolean finished = session.onClientDied(); if (finished) { mAuthSession = null; + notifyAuthSessionChanged(); } } @@ -1349,6 +1409,16 @@ public class BiometricService extends SystemService { }); } + private void notifyAuthSessionChanged() { + for (BiometricPromptStatusListener listener : mBiometricPromptStatusListeners) { + if (mAuthSession == null) { + listener.notifyBiometricPromptIdle(); + } else { + listener.notifyBiometricPromptShowing(); + } + } + } + /** * handleAuthenticate() (above) which is called from BiometricPrompt determines which * modality/modalities to start authenticating with. authenticateInternal() should only be @@ -1386,6 +1456,7 @@ public class BiometricService extends SystemService { } catch (RemoteException e) { Slog.e(TAG, "RemoteException", e); } + notifyAuthSessionChanged(); } private void handleCancelAuthentication(long requestId) { @@ -1400,6 +1471,7 @@ public class BiometricService extends SystemService { if (finished) { Slog.d(TAG, "handleCancelAuthentication: AuthSession finished"); mAuthSession = null; + notifyAuthSessionChanged(); } } diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig index b537e0eae548daae75f8dda6e1329293b6e55531..b12d831ffe2425339449d20722939f91ee9e8e94 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 78c95ad4576b683a5ced44dbba294014e3a1c0bc..a47135fd032ff8843d42685ad9ed6ef2ebf39f44 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 8a54ae5a6fea4d80be4dcaae2ffec6e80a96853d..037ea38a2d1790f800561d76255db58768384c2b 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 95c49032c029b007f865be5ac70acf1f95958f4d..35e9bcbbf0858fd27fb92328584aa8ae92cf0201 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 4a59c9df9bef2099bde662bd1cb5effc02f70f50..8271380010e273d0019ad0a40e567a90f765cd17 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 694dfd28d0cc374923178d29653adf8373f30fb8..80754702415ac623613642fa638e419da47767ab 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 cc0022703745a540b4f4fc8f6f26769f7505e167..e5d4a635876dd4142db1fcab52f08958e6435ea3 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 573c20fe041b6cc5e4f83f3dce1dbda74e8c3154..d149f5215a7a4d178abdbce13f6f870a69b46be0 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 0000000000000000000000000000000000000000..57f5b34c197a992f4986cb0cff0aaea755d90264 --- /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 29eee6b5bb062fc25630f0412586aa5fbf19ca03..858bb864d4dbef56e43f0000a7c642fb4c450fb5 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 35fc43ae5f7470592a12a6c7ea1a6b9d85f8b5ea..470dc4b7172ce788784a21d12a0168b2ca52f7c6 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 f55cf0549382e3b3708e64a053eb5bb8a615a9cb..dbed1f7a8f9def4fd176ebea80fbf6748ac8a1d0 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 165c3a24104359c517d4a01389d2c511b508a8f8..e404bd2be31e8bbd00b87e1a34548389708864a7 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 ef3b345402bf9efa53a66311baa17383a11742c2..c15049b48bb23ed66b1912a4252b1e0ad062dc06 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 f09d192966f13f61d08d0bc1c88705dfff1e1dc5..e75c6aba1489dbf14e95574c33f804ac18b34ea6 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 cc3118cc343345e60cccaf57e8c8f8e945603faa..dd9c6d50ae9e747b4ecaca287c0dd18ac2834930 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 0512017394af33751abc12036a4e7ad1a723cce7..079388822def3b7fa593df12269be724247ea026 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 1a12fcdf501032f234f4bf4e00990f771b92da82..77b5592c5064433efa45d68863bf71cf7c7cd178 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 8838345de4d65625b6a19af58ef7bc86ec46ecd3..0d6143a7d0f080eba9ddafed5cd973f93beb8960 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 6c143872ff8c19815f70483d2bf89700777da3c5..f6da8726564f152f77650ebe5321b914e76b64c4 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 2ad41c2a7a02b7dc00a4a58ee25456123ec32e82..54e66eb4cca4e5653d15e8368871ed98b9d3c630 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 0000000000000000000000000000000000000000..eecf44b929181bc4eaf4cda0b4d84b05e470c947 --- /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 1499317478aab1b32aba72c47929ffeaeafdd114..46ce0b62e6d51c4345c9dd957dc43eccde9cb210 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 0000000000000000000000000000000000000000..36a9790d2d4b7656651d94029a3ad648c85154e8 --- /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 0000000000000000000000000000000000000000..4a019436cf6f42c9e19897c7411ef329b4e5981a --- /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 55861bb4cc6f20f32c7d3205211ceb78bf049ea2..299a310caee9c8d4bf78a5fbfc9f081a7731ac9f 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 54d1faa39be0af444d327e0f048e47de75b5af36..337c3c299e54a67692b2a846f3099b1187130419 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 4502e5d0c4b639b785f6c39720279a8936e62412..e2413ee1c016241447611429922538e6981fe627 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 46ff6b4fab1a8fa61419509403987a07cc0c5337..06550d8b4fce756e890ea6a576cb527aab76bd41 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 ddae8bedf7c176b7fb52a0d754066b8606dd319e..ce693ff58e19359d281c0b7af618624592f882be 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 ff9127f516af187a215ff50bb93a8b3ee8adc22f..5edc2ca080ad25aad68deb319a8f7a8d5bcb9221 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 e42b66472cbe197b2d5a6f79e489616837ffdeee..9985b06833c947c70d2a5e279e18dc5cb5eb648b 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 d559bb1d72f19320aa4be75cbdf19cf42d1cabd4..4f08f6fbde54fc6da06562f8ca7385374ef7991a 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 7a620349075c7de1f8df92b7029310756036fbd9..ec225a60d54be8464f3832d39a4ced1fc3ba4324 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 afa62e2e3173d5ad335e16a75a20fb61f01bddbf..23d8e1e63f639e22549d841f2867fe254134b38a 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 56b85ceb8e6bebb2a767bc5f40fa6c899afd22f7..893cb8f9b4fce62cef48389f6f9ebc19875b0157 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 0000000000000000000000000000000000000000..b48d232e65afebab4381a27195b9c40871474d12 --- /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 a655f3601a07cd08de32f8ec7ba6f8470f1c51c4..8bfa560b80317c42cfedbe6538cff73c0ff8874f 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 0000000000000000000000000000000000000000..c3e5cbe7daf4bd59eda6aa3d243b119e5e4397f9 --- /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 36d56c8a1544f2c9edeb923e5f8605231d96db5b..0730c672acd90ac42fbf2566b07f87154f59f0f6 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/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java index d97c8e71c73c34bdc6989930f3a539ba79e494d9..5e6ff4683850c65fcd0402e35e98a76e03f4d9bd 100644 --- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java +++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java @@ -30,8 +30,7 @@ import java.util.Arrays; class DisplayManagerShellCommand extends ShellCommand { private static final String TAG = "DisplayManagerShellCommand"; - private static final String NOTIFICATION_TYPES = - "on-hotplug-error, on-link-training-failure, on-cable-dp-incapable"; + private static final String NOTIFICATION_TYPES = "on-hotplug-error"; private final DisplayManagerService mService; private final DisplayManagerFlags mFlags; @@ -194,12 +193,6 @@ class DisplayManagerShellCommand extends ShellCommand { case "on-hotplug-error": mService.getDisplayNotificationManager().onHotplugConnectionError(); break; - case "on-link-training-failure": - mService.getDisplayNotificationManager().onDisplayPortLinkTrainingFailure(); - break; - case "on-cable-dp-incapable": - mService.getDisplayNotificationManager().onCableNotCapableDisplayPort(); - break; default: getErrPrintWriter().println( "Error: unexpected notification type=" + notificationType + ", use one of: " diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java index 03b0cfc3d8448d279ede4eb3ecd6da3dcc2af4b1..e3aa161f001ae95df4be914657b34bc996fdd039 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -234,7 +234,9 @@ public final class ColorDisplayService extends SystemService { } } - @VisibleForTesting void onUserChanged(int userHandle) { + // should be called in handler thread (same thread that started animation) + @VisibleForTesting + void onUserChanged(int userHandle) { final ContentResolver cr = getContext().getContentResolver(); if (mCurrentUser != UserHandle.USER_NULL) { @@ -473,6 +475,15 @@ public final class ColorDisplayService extends SystemService { } } + // should be called in handler thread (same thread that started animation) + @VisibleForTesting + void cancelAllAnimators() { + mNightDisplayTintController.cancelAnimator(); + mGlobalSaturationTintController.cancelAnimator(); + mReduceBrightColorsTintController.cancelAnimator(); + mDisplayWhiteBalanceTintController.cancelAnimator(); + } + private boolean resetReduceBrightColors() { if (mCurrentUser == UserHandle.USER_NULL) { return false; diff --git a/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java b/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java deleted file mode 100644 index f683e8104889478935a921b44cd83f2c3ba19884..0000000000000000000000000000000000000000 --- a/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.display.notifications; - -import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED; -import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE; -import static android.hardware.usb.DisplayPortAltModeInfo.LINK_TRAINING_STATUS_FAILURE; - -import android.annotation.NonNull; -import android.annotation.SuppressLint; -import android.content.Context; -import android.hardware.usb.DisplayPortAltModeInfo; -import android.hardware.usb.UsbManager; -import android.hardware.usb.UsbManager.DisplayPortAltModeInfoListener; -import android.util.Slog; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.display.feature.DisplayManagerFlags; - -/** - * Detects usb issues related to an external display connected. - */ -public class ConnectedDisplayUsbErrorsDetector implements DisplayPortAltModeInfoListener { - private static final String TAG = "ConnectedDisplayUsbErrorsDetector"; - - /** - * Dependency injection for {@link ConnectedDisplayUsbErrorsDetector}. - */ - public interface Injector { - - /** - * @return {@link UsbManager} service. - */ - UsbManager getUsbManager(); - } - - /** - * USB errors listener - */ - public interface Listener { - - /** - * Link training failure callback. - */ - void onDisplayPortLinkTrainingFailure(); - - /** - * DisplayPort capable device plugged-in, but cable is not supporting DisplayPort. - */ - void onCableNotCapableDisplayPort(); - } - - private Listener mListener; - private final Injector mInjector; - private final Context mContext; - private final boolean mIsConnectedDisplayErrorHandlingEnabled; - - ConnectedDisplayUsbErrorsDetector(@NonNull final DisplayManagerFlags flags, - @NonNull final Context context) { - this(flags, context, () -> context.getSystemService(UsbManager.class)); - } - - @VisibleForTesting - ConnectedDisplayUsbErrorsDetector(@NonNull final DisplayManagerFlags flags, - @NonNull final Context context, @NonNull final Injector injector) { - mContext = context; - mInjector = injector; - mIsConnectedDisplayErrorHandlingEnabled = - flags.isConnectedDisplayErrorHandlingEnabled(); - } - - /** Register listener for usb error events. */ - @SuppressLint("AndroidFrameworkRequiresPermission") - void registerListener(final Listener listener) { - if (!mIsConnectedDisplayErrorHandlingEnabled) { - return; - } - - final var usbManager = mInjector.getUsbManager(); - if (usbManager == null) { - Slog.e(TAG, "UsbManager is null"); - return; - } - - mListener = listener; - - try { - usbManager.registerDisplayPortAltModeInfoListener(mContext.getMainExecutor(), this); - } catch (IllegalStateException e) { - Slog.e(TAG, "Failed to register listener", e); - } - } - - /** - * Callback upon changes in {@link DisplayPortAltModeInfo}. - * @param portId String describing the {@link android.hardware.usb.UsbPort} that was changed. - * @param info New {@link DisplayPortAltModeInfo} for the corresponding portId. - */ - @Override - public void onDisplayPortAltModeInfoChanged(@NonNull String portId, - @NonNull DisplayPortAltModeInfo info) { - if (mListener == null) { - return; - } - - if (DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED == info.getPartnerSinkStatus() - && DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE == info.getCableStatus() - ) { - mListener.onCableNotCapableDisplayPort(); - return; - } - - if (LINK_TRAINING_STATUS_FAILURE == info.getLinkTrainingStatus()) { - mListener.onDisplayPortLinkTrainingFailure(); - return; - } - } -} diff --git a/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java index 5cdef38cd45cb994bced4489d16620f8ec2cf3be..7d83e90eda9d272be00a88dc52a1cb9ffcebde21 100644 --- a/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java +++ b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java @@ -34,16 +34,12 @@ import com.android.server.display.feature.DisplayManagerFlags; /** * Manages notifications for {@link com.android.server.display.DisplayManagerService}. */ -public class DisplayNotificationManager implements ConnectedDisplayUsbErrorsDetector.Listener { +public class DisplayNotificationManager { /** Dependency injection interface for {@link DisplayNotificationManager} */ public interface Injector { /** Get {@link NotificationManager} service or null if not available. */ @Nullable NotificationManager getNotificationManager(); - - /** Get {@link ConnectedDisplayUsbErrorsDetector} or null if not available. */ - @Nullable - ConnectedDisplayUsbErrorsDetector getUsbErrorsDetector(); } private static final String TAG = "DisplayNotificationManager"; @@ -56,22 +52,9 @@ public class DisplayNotificationManager implements ConnectedDisplayUsbErrorsDete private final Context mContext; private final boolean mConnectedDisplayErrorHandlingEnabled; private NotificationManager mNotificationManager; - private ConnectedDisplayUsbErrorsDetector mConnectedDisplayUsbErrorsDetector; public DisplayNotificationManager(final DisplayManagerFlags flags, final Context context) { - this(flags, context, new Injector() { - @Nullable - @Override - public NotificationManager getNotificationManager() { - return context.getSystemService(NotificationManager.class); - } - - @Nullable - @Override - public ConnectedDisplayUsbErrorsDetector getUsbErrorsDetector() { - return new ConnectedDisplayUsbErrorsDetector(flags, context); - } - }); + this(flags, context, () -> context.getSystemService(NotificationManager.class)); } @VisibleForTesting @@ -92,44 +75,6 @@ public class DisplayNotificationManager implements ConnectedDisplayUsbErrorsDete Slog.e(TAG, "onBootCompleted: NotificationManager is null"); return; } - - mConnectedDisplayUsbErrorsDetector = mInjector.getUsbErrorsDetector(); - if (mConnectedDisplayUsbErrorsDetector != null) { - mConnectedDisplayUsbErrorsDetector.registerListener(this); - } - } - - /** - * Display error notification upon DisplayPort link training failure. - */ - @Override - public void onDisplayPortLinkTrainingFailure() { - if (!mConnectedDisplayErrorHandlingEnabled) { - Slog.d(TAG, "onDisplayPortLinkTrainingFailure:" - + " mConnectedDisplayErrorHandlingEnabled is false"); - return; - } - - sendErrorNotification(createErrorNotification( - R.string.connected_display_unavailable_notification_title, - R.string.connected_display_unavailable_notification_content)); - } - - /** - * Display error notification upon cable not capable of DisplayPort connected to a device - * capable of DisplayPort. - */ - @Override - public void onCableNotCapableDisplayPort() { - if (!mConnectedDisplayErrorHandlingEnabled) { - Slog.d(TAG, "onCableNotCapableDisplayPort:" - + " mConnectedDisplayErrorHandlingEnabled is false"); - return; - } - - sendErrorNotification(createErrorNotification( - R.string.connected_display_cable_dont_support_displays_notification_title, - R.string.connected_display_cable_dont_support_displays_notification_content)); } /** diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java index 1c5ecb70a3aefb0a2e432f3ed54a29d76c523c9e..1e8b387fc1898a95fcda82e1e813140ffe0d3bdb 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java @@ -45,7 +45,7 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.widget.LockPatternUtils; import com.android.server.LocalServices; -import com.android.server.PersistentDataBlockManagerInternal; +import com.android.server.pdb.PersistentDataBlockManagerInternal; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 95ca08cc7fe9b19ef191328ce4cb900e4e8ecb70..a158b18d91b44c5fc7585d24586f4aed24b7a517 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -97,20 +97,23 @@ import java.util.concurrent.CopyOnWriteArrayList; public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionRecordImpl { /** - * {@link MediaSession#setMediaButtonBroadcastReceiver(ComponentName)} throws an {@link - * IllegalArgumentException} if the provided {@link ComponentName} does not resolve to a valid - * {@link android.content.BroadcastReceiver broadcast receiver} for apps targeting Android U and - * above. For apps targeting Android T and below, the request will be ignored. + * {@link android.media.session.MediaSession#setMediaButtonBroadcastReceiver( + * android.content.ComponentName)} throws an {@link + * java.lang.IllegalArgumentException} if the provided {@link android.content.ComponentName} + * does not resolve to a valid {@link android.content.BroadcastReceiver broadcast receiver} + * for apps targeting Android U and above. For apps targeting Android T and below, the request + * will be ignored. */ @ChangeId @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) static final long THROW_FOR_INVALID_BROADCAST_RECEIVER = 270049379L; /** - * {@link MediaSession#setMediaButtonReceiver(PendingIntent)} throws an {@link - * IllegalArgumentException} if the provided {@link PendingIntent} targets an {@link - * android.app.Activity activity} for apps targeting Android V and above. For apps targeting - * Android U and below, the request will be ignored. + * {@link android.media.session.MediaSession#setMediaButtonReceiver(android.app.PendingIntent)} + * throws an {@link java.lang.IllegalArgumentException} if the provided + * {@link android.app.PendingIntent} targets an {@link android.app.Activity activity} for + * apps targeting Android V and above. For apps targeting Android U and below, the request will + * be ignored. */ @ChangeId @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 803ab28f2a655d1a3584acafce802b33b0057491..0e8f90795ef930a82cea2be5cee199a4f2b7b1d3 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -19,6 +19,7 @@ package com.android.server.media; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.os.UserHandle.ALL; import static android.os.UserHandle.CURRENT; + import static com.android.server.media.MediaKeyDispatcher.KEY_EVENT_LONG_PRESS; import static com.android.server.media.MediaKeyDispatcher.isDoubleTapOverridden; import static com.android.server.media.MediaKeyDispatcher.isLongPressOverridden; @@ -1904,6 +1905,15 @@ public class MediaSessionService extends SystemService implements Monitor { keyEvent, AudioManager.USE_DEFAULT_STREAM_TYPE, false); return; } + if (Flags.fallbackToDefaultHandlingWhenMediaSessionHasFixedVolumeHandling() + && !record.canHandleVolumeKey()) { + Log.d(TAG, "Session with packageName=" + record.getPackageName() + + " doesn't support volume adjustment." + + " Fallbacks to the default handling."); + dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid, true, + keyEvent, AudioManager.USE_DEFAULT_STREAM_TYPE, false); + return; + } switch (keyEvent.getAction()) { case KeyEvent.ACTION_DOWN: { int direction = 0; diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index 13d16629460376f77b3c2629772c542100ed052b..8cbc368467bb96a6456ba282a08f88b72a1fed09 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -76,6 +76,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.Watchdog; @@ -134,6 +135,7 @@ public final class MediaProjectionManagerService extends SystemService private final MediaRouter mMediaRouter; private final MediaRouterCallback mMediaRouterCallback; + private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; private MediaRouter.RouteInfo mMediaRouteInfo; @GuardedBy("mLock") @@ -160,6 +162,7 @@ public final class MediaProjectionManagerService extends SystemService mWmInternal = LocalServices.getService(WindowManagerInternal.class); mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE); mMediaRouterCallback = new MediaRouterCallback(); + mMediaProjectionMetricsLogger = injector.mediaProjectionMetricsLogger(); Watchdog.getInstance().addMonitor(this); } @@ -193,6 +196,10 @@ public final class MediaProjectionManagerService extends SystemService Looper createCallbackLooper() { return Looper.getMainLooper(); } + + MediaProjectionMetricsLogger mediaProjectionMetricsLogger() { + return MediaProjectionMetricsLogger.getInstance(); + } } @Override @@ -372,6 +379,10 @@ public final class MediaProjectionManagerService extends SystemService if (mProjectionGrant != null) { // Cache the session details. mProjectionGrant.mSession = incomingSession; + mMediaProjectionMetricsLogger.notifyProjectionStateChange( + mProjectionGrant.uid, + FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS, + FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN); dispatchSessionSet(mProjectionGrant.getProjectionInfo(), incomingSession); } return true; @@ -817,6 +828,19 @@ public final class MediaProjectionManagerService extends SystemService } } + @Override // Binder call + @EnforcePermission(MANAGE_MEDIA_PROJECTION) + public void notifyPermissionRequestStateChange(int hostUid, int state, + int sessionCreationSource) { + notifyPermissionRequestStateChange_enforcePermission(); + final long token = Binder.clearCallingIdentity(); + try { + mMediaProjectionMetricsLogger.notifyProjectionStateChange(hostUid, state, sessionCreationSource); + } finally { + Binder.restoreCallingIdentity(token); + } + } + @Override // Binder call public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java new file mode 100644 index 0000000000000000000000000000000000000000..f18ecad09c42e64e4ba032cd384b57c4a9414ca9 --- /dev/null +++ b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java @@ -0,0 +1,50 @@ +/* + * 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.media.projection; + + +import com.android.internal.util.FrameworkStatsLog; + +/** + * Class for emitting logs describing a MediaProjection session. + */ +public class MediaProjectionMetricsLogger { + private static MediaProjectionMetricsLogger sSingleton = null; + + public static MediaProjectionMetricsLogger getInstance() { + if (sSingleton == null) { + sSingleton = new MediaProjectionMetricsLogger(); + } + return sSingleton; + } + + void notifyProjectionStateChange(int hostUid, int state, int sessionCreationSource) { + write(hostUid, state, sessionCreationSource); + } + + private void write(int hostUid, int state, int sessionCreationSource) { + FrameworkStatsLog.write( + /* code */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED, + /* session_id */ 123, + /* state */ state, + /* previous_state */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN, + /* host_uid */ hostUid, + /* target_uid */ -1, + /* time_since_last_active */ 0, + /* creation_source */ sessionCreationSource); + } +} diff --git a/services/core/java/com/android/server/oemlock/OemLockService.java b/services/core/java/com/android/server/oemlock/OemLockService.java index 4c6110b99efd32c3effb447843e8742cc1e78e87..d00268899a07c12b17e50abca294bcf7861b7b1d 100644 --- a/services/core/java/com/android/server/oemlock/OemLockService.java +++ b/services/core/java/com/android/server/oemlock/OemLockService.java @@ -35,8 +35,8 @@ import android.service.oemlock.IOemLockService; import android.util.Slog; import com.android.server.LocalServices; -import com.android.server.PersistentDataBlockManagerInternal; import com.android.server.SystemService; +import com.android.server.pdb.PersistentDataBlockManagerInternal; import com.android.server.pm.UserManagerInternal; import com.android.server.pm.UserManagerInternal.UserRestrictionsListener; import com.android.server.pm.UserRestrictionsUtils; diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java index c7c8136ff0daeab53765bc909c7ba76661fb9fb5..79c13461fc6bb834dbb48414821bc9b25a31c703 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/pdb/OWNERS b/services/core/java/com/android/server/pdb/OWNERS new file mode 100644 index 0000000000000000000000000000000000000000..6f322eea0f2e8738e5fcd3cc5a989bbc48600ad2 --- /dev/null +++ b/services/core/java/com/android/server/pdb/OWNERS @@ -0,0 +1,2 @@ +victorhsieh@google.com +swillden@google.com diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java similarity index 98% rename from services/core/java/com/android/server/PersistentDataBlockManagerInternal.java rename to services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java index 21fa9f9a9401d4d0de1d152640e2ade13e226aca..66ad7169d6ecc4f9a27709b60b3f86f6267119fb 100644 --- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java +++ b/services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.pdb; /** * Internal interface for storing and retrieving persistent data. diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java similarity index 99% rename from services/core/java/com/android/server/PersistentDataBlockService.java rename to services/core/java/com/android/server/pdb/PersistentDataBlockService.java index 754a7ede800694da7da89ec5bbca2376a3b8a57b..b006ac8625339718ea7553bc07d92985f07b2094 100644 --- a/services/core/java/com/android/server/PersistentDataBlockService.java +++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.pdb; import static com.android.internal.util.Preconditions.checkArgument; @@ -37,6 +37,9 @@ import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.DumpUtils; import com.android.server.pm.UserManagerInternal; +import com.android.server.LocalServices; +import com.android.server.SystemServerInitThreadPool; +import com.android.server.SystemService; import libcore.io.IoUtils; diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index d16a81267370eabd8a9301348be6afa970c56667..d804e01aa31e79646d8d63ea7b45e754401c2d79 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -444,7 +444,7 @@ public class PackageInfoUtils { updateApplicationInfo(info, flags, state); - initForUser(info, pkg, userId); + initForUser(info, pkg, userId, state); // TODO(b/135203078): Remove PackageParser1/toAppInfoWithoutState and clean all this up PackageStateUnserialized pkgState = pkgSetting.getTransientState(); @@ -690,7 +690,7 @@ public class PackageInfoUtils { info.splitDependencies = pkg.getSplitDependencies().size() == 0 ? null : pkg.getSplitDependencies(); - initForUser(info, pkg, userId); + initForUser(info, pkg, userId, state); info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi(); info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi(); @@ -1006,7 +1006,7 @@ public class PackageInfoUtils { } private static void initForUser(ApplicationInfo output, AndroidPackage input, - @UserIdInt int userId) { + @UserIdInt int userId, PackageUserStateInternal state) { PackageImpl pkg = ((PackageImpl) input); String packageName = input.getPackageName(); output.uid = UserHandle.getUid(userId, UserHandle.getAppId(input.getUid())); @@ -1016,6 +1016,13 @@ public class PackageInfoUtils { return; } + if (android.content.pm.Flags.nullableDataDir() + && !state.isInstalled() && !state.dataExists()) { + // The data dir has been deleted + output.dataDir = null; + return; + } + // For performance reasons, all these paths are built as strings if (userId == UserHandle.USER_SYSTEM) { output.credentialProtectedDataDir = @@ -1050,7 +1057,7 @@ public class PackageInfoUtils { // This duplicates the ApplicationInfo variant because it uses field assignment and the classes // don't inherit from each other, unfortunately. Consolidating logic would introduce overhead. private static void initForUser(InstrumentationInfo output, AndroidPackage input, - @UserIdInt int userId) { + @UserIdInt int userId, PackageUserStateInternal state) { PackageImpl pkg = ((PackageImpl) input); String packageName = input.getPackageName(); if ("android".equals(packageName)) { @@ -1058,6 +1065,13 @@ public class PackageInfoUtils { return; } + if (android.content.pm.Flags.nullableDataDir() + && !state.isInstalled() && !state.dataExists()) { + // The data dir has been deleted + output.dataDir = null; + return; + } + // For performance reasons, all these paths are built as strings if (userId == UserHandle.USER_SYSTEM) { output.credentialProtectedDataDir = @@ -1089,12 +1103,23 @@ public class PackageInfoUtils { } } - @NonNull + /** + * Returns the data dir of the app for the target user. Return null if the app isn't installed + * on the target user and doesn't have a data dir on the target user. + */ + @Nullable public static File getDataDir(PackageStateInternal ps, int userId) { if ("android".equals(ps.getPackageName())) { return Environment.getDataSystemDirectory(); } + if (android.content.pm.Flags.nullableDataDir() + && !ps.getUserStateOrDefault(userId).isInstalled() + && !ps.getUserStateOrDefault(userId).dataExists()) { + // The app has been uninstalled for the user and the data dir has been deleted + return null; + } + if (ps.isDefaultToDeviceProtectedStorage() && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) { return Environment.getDataUserDePackageDirectory(ps.getVolumeUuid(), userId, diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig index d61bebc0c82c71afc9096b4b949faf16d5387695..add806febe670ad2e0760ad99913bceeb9907e1d 100644 --- a/services/core/java/com/android/server/power/stats/flags.aconfig +++ b/services/core/java/com/android/server/power/stats/flags.aconfig @@ -1,5 +1,12 @@ package: "com.android.server.power.optimization" +flag { + name: "power_monitor_api" + namespace: "power_optimization" + description: "Feature flag for ODPM API" + bug: "295027807" +} + flag { name: "streamlined_battery_stats" namespace: "power_optimization" diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java index 5609f6975bc47112802806836284608f560b44fa..77290fd944ebd7090bac90099e7c1fc7616d4f12 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsService.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java @@ -694,7 +694,7 @@ public class PowerStatsService extends SystemService { Log.d(TAG, String.format(Locale.ENGLISH, "Monitor=%s timestamp=%d energy=%d" + " uid=%d noise=%.1f%% returned=%d", - state.powerMonitor.name, + state.powerMonitor.getName(), state.timestampMs, state.energyUws, callingUid, @@ -728,7 +728,7 @@ public class PowerStatsService extends SystemService { } for (PowerMonitorState powerMonitorState : powerMonitorStates) { - if (powerMonitorState.powerMonitor.type + if (powerMonitorState.powerMonitor.getType() == PowerMonitor.POWER_MONITOR_TYPE_CONSUMER) { for (EnergyConsumerResult energyConsumerResult : energyConsumerResults) { if (energyConsumerResult.id == powerMonitorState.id) { @@ -754,7 +754,7 @@ public class PowerStatsService extends SystemService { } for (PowerMonitorState powerMonitorState : powerMonitorStates) { - if (powerMonitorState.powerMonitor.type + if (powerMonitorState.powerMonitor.getType() == PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) { for (EnergyMeasurement energyMeasurement : energyMeasurements) { if (energyMeasurement.id == powerMonitorState.id) { @@ -773,7 +773,7 @@ public class PowerStatsService extends SystemService { @PowerMonitor.PowerMonitorType int type) { int count = 0; for (PowerMonitorState monitorState : powerMonitorStates) { - if (monitorState.powerMonitor.type == type) { + if (monitorState.powerMonitor.getType() == type) { count++; } } @@ -785,7 +785,7 @@ public class PowerStatsService extends SystemService { int[] ids = new int[count]; int index = 0; for (PowerMonitorState monitorState : powerMonitorStates) { - if (monitorState.powerMonitor.type == type) { + if (monitorState.powerMonitor.getType() == type) { ids[index++] = monitorState.id; } } diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java index c9db343697df1bad24dd4ca2513a6159b8a0517c..0656a6a7d3e915dbcf3fba9301909aea984bf3ea 100644 --- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java +++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java @@ -174,8 +174,6 @@ public final class SensorPrivacyService extends SystemService { private CallStateHelper mCallStateHelper; private KeyguardManager mKeyguardManager; - private SafetyCenterManager mSafetyCenterManager; - private int mCurrentUser = USER_NULL; public SensorPrivacyService(Context context) { @@ -191,7 +189,6 @@ public final class SensorPrivacyService extends SystemService { mTelephonyManager = context.getSystemService(TelephonyManager.class); mPackageManagerInternal = getLocalService(PackageManagerInternal.class); mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl(); - mSafetyCenterManager = mContext.getSystemService(SafetyCenterManager.class); } @Override @@ -656,7 +653,9 @@ public final class SensorPrivacyService extends SystemService { String contentTitle = getUiContext().getString(messageRes); Spanned contentText = Html.fromHtml(getUiContext().getString( R.string.sensor_privacy_start_use_notification_content_text, packageLabel), 0); - String action = mSafetyCenterManager.isSafetyCenterEnabled() + SafetyCenterManager safetyCenterManager = + mContext.getSystemService(SafetyCenterManager.class); + String action = safetyCenterManager.isSafetyCenterEnabled() ? Settings.ACTION_PRIVACY_CONTROLS : Settings.ACTION_PRIVACY_SETTINGS; PendingIntent contentIntent = PendingIntent.getActivity(mContext, sensor, diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java index bfe34049e1e569ff518c942f036925a8b9976aa2..9a9b836028322c99dd719801beacb876c1c07332 100644 --- a/services/core/java/com/android/server/testharness/TestHarnessModeService.java +++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java @@ -41,8 +41,8 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.widget.LockPatternUtils; import com.android.server.LocalServices; -import com.android.server.PersistentDataBlockManagerInternal; import com.android.server.SystemService; +import com.android.server.pdb.PersistentDataBlockManagerInternal; import com.android.server.pm.UserManagerInternal; import java.io.ByteArrayInputStream; diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java index a5c0fb3c46afc9c85fa086c4033ed205076a5f95..cddc79db6106a4e24279b80de80d5a588b5b42ca 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -1047,6 +1047,16 @@ public class TunerResourceManagerService extends SystemService implements IBinde // in use frontends when no available frontend has been found. int priority = getFrontendHighestClientPriority(fr.getOwnerClientId()); if (currentLowestPriority > priority) { + // we need to check the max used num if the target frontend type is not + // currently in primary use (and simply blocked due to exclusive group) + ClientProfile targetOwnerProfile = getClientProfile(fr.getOwnerClientId()); + int primaryFeId = targetOwnerProfile.getPrimaryFrontend(); + FrontendResource primaryFe = getFrontendResource(primaryFeId); + if (fr.getType() != primaryFe.getType() + && isFrontendMaxNumUseReached(fr.getType())) { + continue; + } + // update the target frontend inUseLowestPriorityFrHandle = fr.getHandle(); currentLowestPriority = priority; isRequestFromSameProcess = (requestClient.getProcessId() diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java index e4f960763d5490809097208d887abfafd702f8a3..a34621642bcd4393171d28e08dfb5fae4502d47f 100644 --- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java +++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java @@ -19,6 +19,7 @@ package com.android.server.vibrator; import android.annotation.Nullable; import android.content.res.Resources; import android.os.VibrationEffect; +import android.os.vibrator.Flags; import android.os.VibratorInfo; import android.os.vibrator.persistence.ParsedVibration; import android.os.vibrator.persistence.VibrationXmlParser; @@ -127,6 +128,10 @@ final class HapticFeedbackCustomization { VibrationXmlParser.VibrationXmlParserException, XmlParserException, XmlPullParserException { + if (!Flags.hapticFeedbackVibrationOemCustomizationEnabled()) { + Slog.d(TAG, "Haptic feedback customization feature is not enabled."); + return null; + } String customizationFile = res.getString( com.android.internal.R.string.config_hapticFeedbackCustomizationFile); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 01ea33f1aecd963357ee8b199b2e9a20456bf04a..0718f2f284a5a31e2365397bbf4a8998f52a9ffe 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -2843,7 +2843,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperData systemWallpaper = mWallpaperMap.get(mCurrentUserId); WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId); boolean systemValid = systemWallpaper != null; - boolean lockValid = lockWallpaper != null && !isLockscreenLiveWallpaperEnabled(); + boolean lockValid = lockWallpaper != null && isLockscreenLiveWallpaperEnabled(); return systemValid && lockValid ? new WallpaperData[]{systemWallpaper, lockWallpaper} : systemValid ? new WallpaperData[]{systemWallpaper} : lockValid ? new WallpaperData[]{lockWallpaper} diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index c866dd013af0be3467f73cca786e51bf77657847..ebd21d9a0487d1ba6dbb04462454fe1b43df798d 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/OWNERS b/services/core/java/com/android/server/wm/OWNERS index 458786ffdbc076c85314006e5d1c04919b03beff..f6c364008c6218b4cb9b9f05d06106e4815563bf 100644 --- a/services/core/java/com/android/server/wm/OWNERS +++ b/services/core/java/com/android/server/wm/OWNERS @@ -18,4 +18,4 @@ rgl@google.com yunfanc@google.com per-file BackgroundActivityStartController.java = set noparent -per-file BackgroundActivityStartController.java = brufino@google.com, ogunwale@google.com, louischang@google.com, lus@google.com \ No newline at end of file +per-file BackgroundActivityStartController.java = brufino@google.com, topjohnwu@google.com, achim@google.com, ogunwale@google.com, louischang@google.com, lus@google.com diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index f509463c5409ad3d4699e41e7646188a6c5f087a..de7871e3c231c33f86b913eb5557230bdf35a2d2 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/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java index 00b9b4c490a49dcb82d405244b1186667565f548..5b9acb2f67c403b7d3c4ecf87de2f0cb381a4397 100644 --- a/services/core/java/com/android/server/wm/WindowManagerFlags.java +++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java @@ -45,5 +45,7 @@ class WindowManagerFlags { final boolean mSyncWindowConfigUpdateFlag = Flags.syncWindowConfigUpdateFlag(); + final boolean mWindowStateResizeItemFlag = Flags.windowStateResizeItemFlag(); + /* End Available Flags */ } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 4beec2bc79e6f1d380673bb65d59745a03975563..726d4d7c34e78340581ef8a35c47346b9f2e18be 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -186,6 +186,7 @@ import android.annotation.Nullable; import android.app.ActivityTaskManager; import android.app.AppOpsManager; import android.app.admin.DevicePolicyCache; +import android.app.servertransaction.WindowStateResizeItem; import android.content.Context; import android.content.res.Configuration; import android.graphics.Matrix; @@ -3732,30 +3733,44 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP markRedrawForSyncReported(); - try { - mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration, - getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId, - syncWithBuffers ? mSyncSeqId : -1, isDragResizing); - if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration - .getMergedConfiguration().windowConfiguration.getRotation()) { - mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime(); - ProtoLog.v(WM_DEBUG_ORIENTATION, - "Requested redraw for orientation change: %s", this); - } - - if (mWmService.mAccessibilityController.hasCallbacks()) { - mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(displayId); + if (mWmService.mFlags.mWindowStateResizeItemFlag) { + getProcess().scheduleClientTransactionItem( + WindowStateResizeItem.obtain(mClient, mClientWindowFrames, reportDraw, + mLastReportedConfiguration, getCompatInsetsState(), forceRelayout, + alwaysConsumeSystemBars, displayId, + syncWithBuffers ? mSyncSeqId : -1, isDragResizing)); + onResizePostDispatched(drawPending, prevRotation, displayId); + } else { + // TODO(b/301870955): cleanup after launch + try { + mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration, + getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId, + syncWithBuffers ? mSyncSeqId : -1, isDragResizing); + onResizePostDispatched(drawPending, prevRotation, displayId); + } catch (RemoteException e) { + // Cancel orientation change of this window to avoid blocking unfreeze display. + setOrientationChanging(false); + mLastFreezeDuration = (int) (SystemClock.elapsedRealtime() + - mWmService.mDisplayFreezeTime); + Slog.w(TAG, "Failed to report 'resized' to " + this + " due to " + e); } - } catch (RemoteException e) { - // Cancel orientation change of this window to avoid blocking unfreeze display. - setOrientationChanging(false); - mLastFreezeDuration = (int)(SystemClock.elapsedRealtime() - - mWmService.mDisplayFreezeTime); - Slog.w(TAG, "Failed to report 'resized' to " + this + " due to " + e); } Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } + private void onResizePostDispatched(boolean drawPending, int prevRotation, int displayId) { + if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration + .getMergedConfiguration().windowConfiguration.getRotation()) { + mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime(); + ProtoLog.v(WM_DEBUG_ORIENTATION, + "Requested redraw for orientation change: %s", this); + } + + if (mWmService.mAccessibilityController.hasCallbacks()) { + mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(displayId); + } + } + boolean inRelaunchingActivity() { return mActivityRecord != null && mActivityRecord.isRelaunching(); } diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index bc70658a06c4730644ebd030bd87d75dc7044a45..709d5e39463c6880872d9e80d7561a3d21553e99 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -70,7 +70,7 @@ cc_library_static { "com_android_server_UsbHostManager.cpp", "com_android_server_vibrator_VibratorController.cpp", "com_android_server_vibrator_VibratorManagerService.cpp", - "com_android_server_PersistentDataBlockService.cpp", + "com_android_server_pdb_PersistentDataBlockService.cpp", "com_android_server_am_LowMemDetector.cpp", "com_android_server_pm_PackageManagerShellCommandDataLoader.cpp", "com_android_server_sensor_SensorService.cpp", diff --git a/services/core/jni/com_android_server_PersistentDataBlockService.cpp b/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp similarity index 85% rename from services/core/jni/com_android_server_PersistentDataBlockService.cpp rename to services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp index 97e69fb4bb404f60734afb24910a8840b91d871f..fc5a11360c8cf7b688530e12c11991fe4091c7d0 100644 --- a/services/core/jni/com_android_server_PersistentDataBlockService.cpp +++ b/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp @@ -76,7 +76,7 @@ namespace android { return ret; } - static jlong com_android_server_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env, jclass, jstring jpath) + static jlong com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env, jclass, jstring jpath) { ScopedUtfChars path(env, jpath); int fd = open(path.c_str(), O_RDONLY); @@ -91,7 +91,7 @@ namespace android { return size; } - static int com_android_server_PersistentDataBlockService_wipe(JNIEnv *env, jclass, jstring jpath) { + static int com_android_server_pdb_PersistentDataBlockService_wipe(JNIEnv *env, jclass, jstring jpath) { ScopedUtfChars path(env, jpath); int fd = open(path.c_str(), O_WRONLY); @@ -107,13 +107,13 @@ namespace android { static const JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ - {"nativeGetBlockDeviceSize", "(Ljava/lang/String;)J", (void*)com_android_server_PersistentDataBlockService_getBlockDeviceSize}, - {"nativeWipe", "(Ljava/lang/String;)I", (void*)com_android_server_PersistentDataBlockService_wipe}, + {"nativeGetBlockDeviceSize", "(Ljava/lang/String;)J", (void*)com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize}, + {"nativeWipe", "(Ljava/lang/String;)I", (void*)com_android_server_pdb_PersistentDataBlockService_wipe}, }; - int register_android_server_PersistentDataBlockService(JNIEnv* env) + int register_android_server_pdb_PersistentDataBlockService(JNIEnv* env) { - return jniRegisterNativeMethods(env, "com/android/server/PersistentDataBlockService", + return jniRegisterNativeMethods(env, "com/android/server/pdb/PersistentDataBlockService", sMethods, NELEM(sMethods)); } diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index df4489528be57e3537b631790f879815f26f5288..11734da5b1ac7bc988d563d282bcc8c40ff79133 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -47,7 +47,7 @@ int register_android_server_connectivity_Vpn(JNIEnv* env); int register_android_server_devicepolicy_CryptoTestHelper(JNIEnv*); int register_android_server_tv_TvUinputBridge(JNIEnv* env); int register_android_server_tv_TvInputHal(JNIEnv* env); -int register_android_server_PersistentDataBlockService(JNIEnv* env); +int register_android_server_pdb_PersistentDataBlockService(JNIEnv* env); int register_android_server_Watchdog(JNIEnv* env); int register_android_server_HardwarePropertiesManagerService(JNIEnv* env); int register_android_server_SyntheticPasswordManager(JNIEnv* env); @@ -108,7 +108,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_BatteryStatsService(env); register_android_server_tv_TvUinputBridge(env); register_android_server_tv_TvInputHal(env); - register_android_server_PersistentDataBlockService(env); + register_android_server_pdb_PersistentDataBlockService(env); register_android_server_HardwarePropertiesManagerService(env); register_android_server_storage_AppFuse(env); register_android_server_SyntheticPasswordManager(env); diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp index 68e2c9a57c2415813653d204978a002e2c89c4c2..c7366173ecda653c50202681c8d5fb49287f5032 100644 --- a/services/core/jni/tvinput/JTvInputHal.cpp +++ b/services/core/jni/tvinput/JTvInputHal.cpp @@ -147,7 +147,6 @@ int JTvInputHal::removeStream(int deviceId, int streamId) { } int JTvInputHal::setTvMessageEnabled(int deviceId, int streamId, int type, bool enabled) { - Mutex::Autolock autoLock(&mLock); if (!mTvInput->setTvMessageEnabled(deviceId, streamId, static_cast<AidlTvMessageEventType>(type), enabled) .isOk()) { @@ -188,7 +187,7 @@ static const std::map<std::pair<AidlAudioDeviceType, std::string>, audio_devices void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfoWrapper& info) { { - Mutex::Autolock autoLock(&mLock); + Mutex::Autolock autoLock(&mStreamLock); mConnections.add(info.deviceId, KeyedVector<int, Connection>()); } JNIEnv* env = AndroidRuntime::getJNIEnv(); @@ -275,7 +274,7 @@ void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfoWrapper& info) { void JTvInputHal::onDeviceUnavailable(int deviceId) { { - Mutex::Autolock autoLock(&mLock); + Mutex::Autolock autoLock(&mStreamLock); KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); for (size_t i = 0; i < connections.size(); ++i) { removeStream(deviceId, connections.keyAt(i)); @@ -289,7 +288,7 @@ void JTvInputHal::onDeviceUnavailable(int deviceId) { void JTvInputHal::onStreamConfigurationsChanged(int deviceId, int cableConnectionStatus) { { - Mutex::Autolock autoLock(&mLock); + Mutex::Autolock autoLock(&mStreamLock); KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); for (size_t i = 0; i < connections.size(); ++i) { removeStream(deviceId, connections.keyAt(i)); @@ -330,7 +329,7 @@ void JTvInputHal::onTvMessage(int deviceId, int streamId, AidlTvMessageEventType void JTvInputHal::onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded) { sp<BufferProducerThread> thread; { - Mutex::Autolock autoLock(&mLock); + Mutex::Autolock autoLock(&mStreamLock); KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); Connection& connection = connections.editValueFor(streamId); if (connection.mThread == NULL) { diff --git a/services/core/jni/tvinput/JTvInputHal.h b/services/core/jni/tvinput/JTvInputHal.h index b7b4b1621512bb098bb2f81ef46181d3fa344792..1d8d1629f67d8f6d775cb97dac8b87b9ffcd0d53 100644 --- a/services/core/jni/tvinput/JTvInputHal.h +++ b/services/core/jni/tvinput/JTvInputHal.h @@ -220,7 +220,6 @@ private: void onTvMessage(int deviceId, int streamId, AidlTvMessageEventType type, AidlTvMessage& message, signed char data[], int dataLength); - Mutex mLock; Mutex mStreamLock; jweak mThiz; sp<Looper> mLooper; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 49af89b5f02e8d4970cb8851acce8b6a4ceb802c..5a620a3b87f53649b35054a6a13f715bfef5694c 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -486,7 +486,6 @@ import com.android.server.AlarmManagerInternal; import com.android.server.LocalManagerRegistry; import com.android.server.LocalServices; import com.android.server.LockGuard; -import com.android.server.PersistentDataBlockManagerInternal; import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemService; import com.android.server.SystemServiceManager; @@ -494,6 +493,7 @@ import com.android.server.devicepolicy.ActiveAdmin.TrustAgentInfo; import com.android.server.devicepolicy.flags.FlagUtils; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.net.NetworkPolicyManagerInternal; +import com.android.server.pdb.PersistentDataBlockManagerInternal; import com.android.server.pm.DefaultCrossProfileIntentFilter; import com.android.server.pm.DefaultCrossProfileIntentFiltersUtils; import com.android.server.pm.PackageManagerLocal; diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 59f1edcf309d3f3a95534aabdbeae6e589e06cd6..c26aee8af83e087ea8b81c4bb1f5db7c71add10c 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -161,6 +161,7 @@ import com.android.server.os.BugreportManagerService; import com.android.server.os.DeviceIdentifiersPolicyService; import com.android.server.os.NativeTombstoneManagerService; import com.android.server.os.SchedulingPolicyService; +import com.android.server.pdb.PersistentDataBlockService; import com.android.server.people.PeopleService; import com.android.server.permission.access.AccessCheckingService; import com.android.server.pm.ApexManager; diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index 486ddb4cb354cc735b3104c3db9eb55bb871ae02..a8902fcf77afde2d775f7bb8e1d319c0d658997b 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -1391,7 +1391,6 @@ public class MidiService extends IMidiManager.Stub { private static final String[] EMPTY_STRING_ARRAY = new String[0]; private void addLegacyPackageDeviceServer(ServiceInfo serviceInfo, int userId) { - Log.d(TAG, "addLegacyPackageDeviceServer()" + userId); XmlResourceParser parser = null; try { @@ -1529,7 +1528,6 @@ public class MidiService extends IMidiManager.Stub { @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) private void addUmpPackageDeviceServer(ServiceInfo serviceInfo, int userId) { - Log.d(TAG, "addUmpPackageDeviceServer()" + userId); XmlResourceParser parser = null; try { diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java index c7c09b5deb3591723c931c94c8759a01872bbd8c..ec27f9d220dc0724a6ac6094ac58fd107a5905ca 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java @@ -44,12 +44,12 @@ import android.provider.Settings.System; import android.test.mock.MockContentResolver; import android.view.Display; -import androidx.test.InstrumentationRegistry; +import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; import com.android.internal.util.test.FakeSettingsProvider; -import com.android.server.LocalServices; +import com.android.internal.util.test.LocalServiceKeeperRule; import com.android.server.SystemService; import com.android.server.twilight.TwilightListener; import com.android.server.twilight.TwilightManager; @@ -57,6 +57,7 @@ import com.android.server.twilight.TwilightState; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; @@ -90,9 +91,13 @@ public class ColorDisplayServiceTest { ColorDisplayManager.COLOR_MODE_BOOSTED, }; + @Rule + public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule(); + @Before public void setUp() { - mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); + mContext = Mockito.spy(new ContextWrapper( + InstrumentationRegistry.getInstrumentation().getTargetContext())); doReturn(mContext).when(mContext).getApplicationContext(); final Resources res = Mockito.spy(mContext.getResources()); @@ -112,43 +117,36 @@ public class ColorDisplayServiceTest { doReturn(am).when(mContext).getSystemService(Context.ALARM_SERVICE); mTwilightManager = new MockTwilightManager(); - LocalServices.addService(TwilightManager.class, mTwilightManager); + mLocalServiceKeeperRule.overrideLocalService(TwilightManager.class, mTwilightManager); mDisplayTransformManager = Mockito.mock(DisplayTransformManager.class); doReturn(true).when(mDisplayTransformManager).needsLinearColorMatrix(); - LocalServices.addService(DisplayTransformManager.class, mDisplayTransformManager); + mLocalServiceKeeperRule.overrideLocalService( + DisplayTransformManager.class, mDisplayTransformManager); mDisplayManagerInternal = Mockito.mock(DisplayManagerInternal.class); - LocalServices.removeServiceForTest(DisplayManagerInternal.class); - LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternal); + mLocalServiceKeeperRule.overrideLocalService( + DisplayManagerInternal.class, mDisplayManagerInternal); mCds = new ColorDisplayService(mContext); mBinderService = mCds.new BinderService(); - LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class, + mLocalServiceKeeperRule.overrideLocalService( + ColorDisplayService.ColorDisplayServiceInternal.class, mCds.new ColorDisplayServiceInternal()); } @After public void tearDown() { - /* - * Wait for internal {@link Handler} to finish processing pending messages, so that test - * code can safelyremove {@link DisplayTransformManager} mock from {@link LocalServices}. - */ - mCds.mHandler.runWithScissors(() -> { /* nop */ }, /* timeout */ 1000); + // synchronously cancel all animations + mCds.mHandler.runWithScissors(() -> mCds.cancelAllAnimators(), /* timeout */ 1000); mCds = null; - LocalServices.removeServiceForTest(TwilightManager.class); mTwilightManager = null; - LocalServices.removeServiceForTest(DisplayTransformManager.class); - mUserId = UserHandle.USER_NULL; mContext = null; FakeSettingsProvider.clearSettingsProvider(); - - LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class); - LocalServices.removeServiceForTest(DisplayManagerInternal.class); } @Test @@ -1249,10 +1247,10 @@ public class ColorDisplayServiceTest { private void startService() { Secure.putIntForUser(mContext.getContentResolver(), Secure.USER_SETUP_COMPLETE, 1, mUserId); - InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { - mCds.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); - mCds.onUserChanged(mUserId); - }); + InstrumentationRegistry.getInstrumentation().runOnMainSync( + () -> mCds.onBootPhase(SystemService.PHASE_BOOT_COMPLETED)); + // onUserChanged cancels running animations, and should be called in handler thread + mCds.mHandler.runWithScissors(() -> mCds.onUserChanged(mUserId), 1000); } /** diff --git a/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java deleted file mode 100644 index d5a92cbc927f106c698d45cd6fbac8c5bcb57664..0000000000000000000000000000000000000000 --- a/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.display.notifications; - -import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED; -import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_ENABLED; -import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE; -import static android.hardware.usb.DisplayPortAltModeInfo.LINK_TRAINING_STATUS_FAILURE; - -import static org.junit.Assume.assumeFalse; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.hardware.usb.DisplayPortAltModeInfo; -import android.hardware.usb.UsbManager; - -import androidx.test.core.app.ApplicationProvider; -import androidx.test.filters.SmallTest; - -import com.android.server.display.feature.DisplayManagerFlags; -import com.android.server.display.notifications.ConnectedDisplayUsbErrorsDetector.Injector; - -import com.google.testing.junit.testparameterinjector.TestParameter; -import com.google.testing.junit.testparameterinjector.TestParameterInjector; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * Tests for {@link ConnectedDisplayUsbErrorsDetector} - * Run: atest ConnectedDisplayUsbErrorsDetectorTest - */ -@SmallTest -@RunWith(TestParameterInjector.class) -public class ConnectedDisplayUsbErrorsDetectorTest { - @Mock - private Injector mMockedInjector; - @Mock - private UsbManager mMockedUsbManager; - @Mock - private DisplayManagerFlags mMockedFlags; - @Mock - private ConnectedDisplayUsbErrorsDetector.Listener mMockedListener; - - /** Setup tests. */ - @Before - public void setup() throws Exception { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testNoErrorTypes( - @TestParameter final boolean isUsbManagerAvailable, - @TestParameter final boolean isUsbErrorsNotificationEnabled) { - // This is tested in #testErrorOnUsbCableNotCapableDp and #testErrorOnDpLinkTrainingFailure - assumeFalse(isUsbManagerAvailable && isUsbErrorsNotificationEnabled); - var detector = createErrorsDetector(isUsbManagerAvailable, isUsbErrorsNotificationEnabled); - // None of these should trigger an error now. - detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnUsbCableNotCapableDp()); - detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnDpLinkTrainingFailure()); - verify(mMockedUsbManager, never()).registerDisplayPortAltModeInfoListener(any(), any()); - verify(mMockedListener, never()).onCableNotCapableDisplayPort(); - verify(mMockedListener, never()).onDisplayPortLinkTrainingFailure(); - } - - @Test - public void testErrorOnUsbCableNotCapableDp() { - var detector = createErrorsDetector(/*isUsbManagerAvailable=*/ true, - /*isUsbErrorsNotificationEnabled=*/ true); - detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnUsbCableNotCapableDp()); - verify(mMockedUsbManager).registerDisplayPortAltModeInfoListener(any(), any()); - verify(mMockedListener).onCableNotCapableDisplayPort(); - verify(mMockedListener, never()).onDisplayPortLinkTrainingFailure(); - } - - @Test - public void testErrorOnDpLinkTrainingFailure() { - var detector = createErrorsDetector(/*isUsbManagerAvailable=*/ true, - /*isUsbErrorsNotificationEnabled=*/ true); - detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnDpLinkTrainingFailure()); - verify(mMockedUsbManager).registerDisplayPortAltModeInfoListener(any(), any()); - verify(mMockedListener, never()).onCableNotCapableDisplayPort(); - verify(mMockedListener).onDisplayPortLinkTrainingFailure(); - } - - private ConnectedDisplayUsbErrorsDetector createErrorsDetector( - final boolean isUsbManagerAvailable, - final boolean isConnectedDisplayUsbErrorsNotificationEnabled) { - when(mMockedFlags.isConnectedDisplayErrorHandlingEnabled()) - .thenReturn(isConnectedDisplayUsbErrorsNotificationEnabled); - when(mMockedInjector.getUsbManager()).thenReturn( - (isUsbManagerAvailable) ? mMockedUsbManager : null); - var detector = new ConnectedDisplayUsbErrorsDetector(mMockedFlags, - ApplicationProvider.getApplicationContext(), mMockedInjector); - detector.registerListener(mMockedListener); - return detector; - } - - private DisplayPortAltModeInfo createInfoOnUsbCableNotCapableDp() { - return new DisplayPortAltModeInfo( - DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED, - DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE, -1, false, 0); - } - - private DisplayPortAltModeInfo createInfoOnDpLinkTrainingFailure() { - return new DisplayPortAltModeInfo( - DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED, - DISPLAYPORT_ALT_MODE_STATUS_ENABLED, -1, false, - LINK_TRAINING_STATUS_FAILURE); - } -} diff --git a/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java b/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java index 1d2034be4acb959c25ef5600e348f55749da8716..d7c35ed3d610d030764e40476e9b149eaded17f4 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java @@ -82,36 +82,15 @@ public class DisplayNotificationManagerTest { assertExpectedNotification(); } - @Test - public void testNotificationOnDisplayPortLinkTrainingFailure() { - var dnm = createDisplayNotificationManager(/*isNotificationManagerAvailable=*/ true, - /*isErrorHandlingEnabled=*/ true); - dnm.onDisplayPortLinkTrainingFailure(); - assertExpectedNotification(); - } - - @Test - public void testNotificationOnCableNotCapableDisplayPort() { - var dnm = createDisplayNotificationManager(/*isNotificationManagerAvailable=*/ true, - /*isErrorHandlingEnabled=*/ true); - dnm.onCableNotCapableDisplayPort(); - assertExpectedNotification(); - } - @Test public void testNoErrorNotification( @TestParameter final boolean isNotificationManagerAvailable, @TestParameter final boolean isErrorHandlingEnabled) { - /* This case is tested by #testNotificationOnHotplugConnectionError, - #testNotificationOnDisplayPortLinkTrainingFailure, - #testNotificationOnCableNotCapableDisplayPort */ + /* This case is tested by #testNotificationOnHotplugConnectionError */ assumeFalse(isNotificationManagerAvailable && isErrorHandlingEnabled); var dnm = createDisplayNotificationManager(isNotificationManagerAvailable, isErrorHandlingEnabled); - // None of these methods should trigger a notification now. dnm.onHotplugConnectionError(); - dnm.onDisplayPortLinkTrainingFailure(); - dnm.onCableNotCapableDisplayPort(); verify(mMockedNotificationManager, never()).notify(anyString(), anyInt(), any()); } @@ -122,8 +101,6 @@ public class DisplayNotificationManagerTest { isErrorHandlingEnabled); when(mMockedInjector.getNotificationManager()).thenReturn( (isNotificationManagerAvailable) ? mMockedNotificationManager : null); - // Usb errors detector is tested in ConnectedDisplayUsbErrorsDetectorTest - when(mMockedInjector.getUsbErrorsDetector()).thenReturn(/* usbErrorsDetector= */ null); final var displayNotificationManager = new DisplayNotificationManager(mMockedFlags, ApplicationProvider.getApplicationContext(), mMockedInjector); displayNotificationManager.onBootCompleted(); 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 5b519633081a0bd12710ad6ed4458c46c421d824..21e3b34ed61a7f0a4a21cbac5c585866cb1dc9c0 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; @@ -798,47 +794,6 @@ public final class AlarmManagerServiceTest { assertEquals(70, mService.mConstants.TEMPORARY_QUOTA_BUMP); } - @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); @@ -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); @@ -3051,136 +2973,12 @@ 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/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java index 2ffe4aacda734ee782201a4dc2b8877b8cbe00d7..df46054f0f6f01db44190aa39469e610a0c09677 100644 --- a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java +++ b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java @@ -1079,7 +1079,7 @@ public class PowerStatsServiceTest { GetSupportedPowerMonitorsResult result = new GetSupportedPowerMonitorsResult(); mService.getSupportedPowerMonitorsImpl(result); assertThat(result.powerMonitors).isNotNull(); - assertThat(Arrays.stream(result.powerMonitors).map(pm -> pm.name).toList()) + assertThat(Arrays.stream(result.powerMonitors).map(PowerMonitor::getName).toList()) .containsAtLeast( "energyconsumer0", "BLUETOOTH/1", @@ -1130,7 +1130,7 @@ public class PowerStatsServiceTest { mService.getSupportedPowerMonitorsImpl(supportedPowerMonitorsResult); Map<String, PowerMonitor> map = Arrays.stream(supportedPowerMonitorsResult.powerMonitors) - .collect(Collectors.toMap(pm -> pm.name, pm -> pm)); + .collect(Collectors.toMap(PowerMonitor::getName, pm -> pm)); PowerMonitor consumer1 = map.get("energyconsumer0"); PowerMonitor consumer2 = map.get("BLUETOOTH/1"); PowerMonitor measurement1 = map.get("[channelname0]:channelsubsystem0"); @@ -1196,6 +1196,6 @@ public class PowerStatsServiceTest { supportedPowerMonitorsResult = new GetSupportedPowerMonitorsResult(); mService.getSupportedPowerMonitorsImpl(supportedPowerMonitorsResult); assertThat(Arrays.stream(supportedPowerMonitorsResult.powerMonitors) - .map(pm -> pm.name).toList()).contains("energyconsumer0"); + .map(PowerMonitor::getName).toList()).contains("energyconsumer0"); } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index 49f22eca56438c15b8820f947d945e92a6883b35..cf315a4e0b9a241d78a8f4e98c8319ad5e11e29f 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -33,6 +33,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; @@ -53,12 +54,18 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.res.XmlResourceParser; import android.graphics.drawable.Icon; import android.hardware.display.DisplayManagerGlobal; +import android.net.Uri; import android.os.Bundle; import android.os.IBinder; import android.os.LocaleList; import android.os.UserHandle; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.testing.TestableContext; import android.view.Display; @@ -93,8 +100,13 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; /** * APCT tests for {@link AccessibilityManagerService}. @@ -104,6 +116,10 @@ public class AccessibilityManagerServiceTest { public final A11yTestableContext mTestableContext = new A11yTestableContext( ApplicationProvider.getApplicationContext()); + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + private static final int ACTION_ID = 20; private static final String LABEL = "label"; private static final String INTENT_ACTION = "TESTACTION"; @@ -204,6 +220,8 @@ public class AccessibilityManagerServiceTest { mA11yms.getCurrentUserIdLocked()); when(mMockServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo); mMockResolveInfo.serviceInfo = mock(ServiceInfo.class); + mMockResolveInfo.serviceInfo.packageName = "packageName"; + mMockResolveInfo.serviceInfo.name = "className"; mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class); when(mMockBinder.queryLocalInterface(any())).thenReturn(mMockServiceClient); @@ -581,6 +599,73 @@ public class AccessibilityManagerServiceTest { ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()); } + @Test + @RequiresFlagsDisabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK) + // Test old behavior to validate lock detection for the old (locked access) case. + public void testPackageMonitorScanPackages_scansWhileHoldingLock() { + setupAccessibilityServiceConnection(0); + final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning(); + when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())) + .thenReturn(List.of(mMockResolveInfo)); + when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true); + + final Intent packageIntent = new Intent(Intent.ACTION_PACKAGE_ADDED); + packageIntent.setData(Uri.parse("test://package")); + packageIntent.putExtra(Intent.EXTRA_USER_HANDLE, mA11yms.getCurrentUserIdLocked()); + packageIntent.putExtra(Intent.EXTRA_REPLACING, true); + mA11yms.getPackageMonitor().doHandlePackageEvent(packageIntent); + + assertThat(lockState.get()).containsExactly(true); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK) + public void testPackageMonitorScanPackages_scansWithoutHoldingLock() { + setupAccessibilityServiceConnection(0); + final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning(); + when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())) + .thenReturn(List.of(mMockResolveInfo)); + when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true); + + final Intent packageIntent = new Intent(Intent.ACTION_PACKAGE_ADDED); + packageIntent.setData(Uri.parse("test://package")); + packageIntent.putExtra(Intent.EXTRA_USER_HANDLE, mA11yms.getCurrentUserIdLocked()); + packageIntent.putExtra(Intent.EXTRA_REPLACING, true); + mA11yms.getPackageMonitor().doHandlePackageEvent(packageIntent); + + assertThat(lockState.get()).containsExactly(false); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK) + public void testSwitchUserScanPackages_scansWithoutHoldingLock() { + setupAccessibilityServiceConnection(0); + final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning(); + when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())) + .thenReturn(List.of(mMockResolveInfo)); + when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true); + + mA11yms.switchUser(mA11yms.getCurrentUserIdLocked() + 1); + + assertThat(lockState.get()).containsExactly(false); + } + + // Single package intents can trigger multiple PackageMonitor callbacks. + // Collect the state of the lock in a set, since tests only care if calls + // were all locked or all unlocked. + private AtomicReference<Set<Boolean>> collectLockStateWhilePackageScanning() { + final AtomicReference<Set<Boolean>> lockState = + new AtomicReference<>(new HashSet<Boolean>()); + doAnswer((Answer<XmlResourceParser>) invocation -> { + lockState.updateAndGet(set -> { + set.add(mA11yms.unsafeIsLockHeld()); + return set; + }); + return null; + }).when(mMockResolveInfo.serviceInfo).loadXmlMetaData(any(), any()); + return lockState; + } + private void mockManageAccessibilityGranted(TestableContext context) { context.getTestablePermissions().setPermission(Manifest.permission.MANAGE_ACCESSIBILITY, PackageManager.PERMISSION_GRANTED); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java index 1cd61e90126e34c5686317286db5bf2be63425e0..efcdbd488a3984ce7a082dc34a2b1665170fa6d1 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java @@ -44,6 +44,10 @@ import android.content.Context; import android.graphics.PointF; import android.os.Looper; import android.os.SystemClock; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.DexmakerShareClassLoaderRule; import android.view.InputDevice; import android.view.MotionEvent; @@ -56,6 +60,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.accessibility.AccessibilityTraceManager; import com.android.server.accessibility.EventStreamTransformation; +import com.android.server.accessibility.Flags; import com.android.server.accessibility.utils.GestureLogParser; import com.android.server.testutils.OffsettableClock; @@ -76,6 +81,7 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; + @RunWith(AndroidJUnit4.class) public class TouchExplorerTest { @@ -119,6 +125,9 @@ public class TouchExplorerTest { public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = new DexmakerShareClassLoaderRule(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + /** * {@link TouchExplorer#sendDownForAllNotInjectedPointers} injecting events with the same object * is resulting {@link ArgumentCaptor} to capture events with last state. Before implementation @@ -161,11 +170,16 @@ public class TouchExplorerTest { goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER); // Wait for transiting to touch exploring state. mHandler.fastForward(2 * USER_INTENT_TIMEOUT); - moveEachPointers(mLastEvent, p(10, 10)); - send(mLastEvent); + assertState(STATE_TOUCH_EXPLORING); + // Manually construct the next move event. Using moveEachPointers() will batch the move + // event which produces zero movement for some reason. + float[] x = new float[1]; + float[] y = new float[1]; + x[0] = mLastEvent.getX(0) + mTouchSlop; + y[0] = mLastEvent.getY(0) + mTouchSlop; + send(manyPointerEvent(ACTION_MOVE, x, y)); goToStateClearFrom(STATE_TOUCH_EXPLORING_1FINGER); assertCapturedEvents(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT); - assertState(STATE_TOUCH_EXPLORING); } /** @@ -173,7 +187,34 @@ public class TouchExplorerTest { * change the coordinates. */ @Test - public void testOneFingerMoveWithExtraMoveEvents() { + @RequiresFlagsEnabled(Flags.FLAG_REDUCE_TOUCH_EXPLORATION_SENSITIVITY) + public void testOneFingerMoveWithExtraMoveEvents_generatesOneMoveEvent() { + goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER); + // Inject a set of move events that have the same coordinates as the down event. + moveEachPointers(mLastEvent, p(0, 0)); + send(mLastEvent); + // Wait for transition to touch exploring state. + mHandler.fastForward(2 * USER_INTENT_TIMEOUT); + // Now move for real. + moveAtLeastTouchSlop(mLastEvent); + send(mLastEvent); + // One more move event with no change. + moveEachPointers(mLastEvent, p(0, 0)); + send(mLastEvent); + goToStateClearFrom(STATE_TOUCH_EXPLORING_1FINGER); + assertCapturedEvents( + ACTION_HOVER_ENTER, + ACTION_HOVER_MOVE, + ACTION_HOVER_EXIT); + } + + /** + * Test the case where ACTION_DOWN is followed by a number of ACTION_MOVE events that do not + * change the coordinates. + */ + @Test + @RequiresFlagsDisabled(Flags.FLAG_REDUCE_TOUCH_EXPLORATION_SENSITIVITY) + public void testOneFingerMoveWithExtraMoveEvents_generatesThreeMoveEvent() { goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER); // Inject a set of move events that have the same coordinates as the down event. moveEachPointers(mLastEvent, p(0, 0)); @@ -181,7 +222,7 @@ public class TouchExplorerTest { // Wait for transition to touch exploring state. mHandler.fastForward(2 * USER_INTENT_TIMEOUT); // Now move for real. - moveEachPointers(mLastEvent, p(10, 10)); + moveAtLeastTouchSlop(mLastEvent); send(mLastEvent); // One more move event with no change. moveEachPointers(mLastEvent, p(0, 0)); @@ -242,7 +283,7 @@ public class TouchExplorerTest { moveEachPointers(mLastEvent, p(0, 0), p(0, 0)); send(mLastEvent); // Now move for real. - moveEachPointers(mLastEvent, p(10, 10), p(10, 10)); + moveEachPointers(mLastEvent, p(mTouchSlop, mTouchSlop), p(mTouchSlop, mTouchSlop)); send(mLastEvent); goToStateClearFrom(STATE_DRAGGING_2FINGERS); assertCapturedEvents(ACTION_DOWN, ACTION_MOVE, ACTION_MOVE, ACTION_MOVE, ACTION_UP); @@ -251,7 +292,7 @@ public class TouchExplorerTest { @Test public void testUpEvent_OneFingerMove_clearStateAndInjectHoverEvents() { goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER); - moveEachPointers(mLastEvent, p(10, 10)); + moveAtLeastTouchSlop(mLastEvent); send(mLastEvent); // Wait 10 ms to make sure that hover enter and exit are not scheduled for the same moment. mHandler.fastForward(10); @@ -277,7 +318,7 @@ public class TouchExplorerTest { // Wait for the finger moving to the second view. mHandler.fastForward(oneThirdUserIntentTimeout); - moveEachPointers(mLastEvent, p(10, 10)); + moveAtLeastTouchSlop(mLastEvent); send(mLastEvent); // Wait for the finger lifting from the second view. @@ -402,7 +443,6 @@ public class TouchExplorerTest { // Manually construct the next move event. Using moveEachPointers() will batch the move // event onto the pointer up event which will mean that the move event still has a pointer // count of 3. - // Todo: refactor to avoid using batching as there is no special reason to do it that way. float[] x = new float[2]; float[] y = new float[2]; x[0] = mLastEvent.getX(0) + 100; @@ -734,6 +774,9 @@ public class TouchExplorerTest { } } + private void moveAtLeastTouchSlop(MotionEvent event) { + moveEachPointers(event, p(2 * mTouchSlop, 0)); + } /** * A {@link android.os.Handler} that doesn't process messages until {@link #fastForward(int)} is * invoked. diff --git a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java index 70527ce2ad328141e747f15e16ba373df219f2f8..44d6760523521b004231c451efcead85a49b0f72 100644 --- a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java @@ -238,7 +238,7 @@ public class AnrTimerTest { } @Override - Handler getHandler(Handler.Callback callback) { + Handler newHandler(Handler.Callback callback) { if (mTestHandler == null) { mTestHandler = new TestHandler(mHandler.getLooper(), callback, mImmediate); } @@ -250,14 +250,18 @@ public class AnrTimerTest { return mTestHandler; } + /** + * This override returns the tracker supplied in the constructor. It does not create a + * new one. + */ @Override - AnrTimer.CpuTracker getTracker() { + AnrTimer.CpuTracker newTracker() { return mTracker; } /** For test purposes, always enable the feature. */ @Override - boolean getFeatureEnabled() { + boolean isFeatureEnabled() { return true; } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index 0230d77e8e14793bf3fb35ea10572b7edf59c6e3..e3e708ec856d5aad4a458d6eabdfdb2360aa1dbf 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -47,6 +47,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; 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.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -64,6 +65,7 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.biometrics.IBiometricPromptStatusListener; import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; @@ -1751,6 +1753,45 @@ public class BiometricServiceTest { verifyNoMoreInteractions(callback); } + @Test + public void testRegisterBiometricPromptOnKeyguardCallback_authenticationAlreadyStarted() + throws Exception { + final IBiometricPromptStatusListener callback = + mock(IBiometricPromptStatusListener.class); + + setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + true /* requireConfirmation */, null /* authenticators */); + mBiometricService.mImpl.registerBiometricPromptStatusListener(callback); + + verify(callback).onBiometricPromptShowing(); + } + + @Test + public void testRegisterBiometricPromptOnKeyguardCallback_startAuth_dismissDialog() + throws Exception { + final IBiometricPromptStatusListener listener = + mock(IBiometricPromptStatusListener.class); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + mBiometricService.mImpl.registerBiometricPromptStatusListener(listener); + waitForIdle(); + + verify(listener).onBiometricPromptIdle(); + + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + true /* requireConfirmation */, null /* authenticators */); + waitForIdle(); + + verify(listener).onBiometricPromptShowing(); + + final byte[] hat = generateRandomHAT(); + mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed( + BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED, hat); + waitForIdle(); + + verify(listener, times(2)).onBiometricPromptIdle(); + } + // Helper methods private int invokeCanAuthenticate(BiometricService service, int authenticators) 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 9e5a0479ea708afe4887752a42023fa7204a5c5e..3a3dd6ea2746d5e10a39d10ee41dd769103f3372 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 ade3e8275157fae878b92249320e28efbd19286d..fbf0e13c2ac9a8e24b6a92272bd18ad40e58e837 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 54d116f0780554420950e0e30cb20aa3ed63823d..128f3149e6d41d5f8853d762999517c470778111 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 0000000000000000000000000000000000000000..c8bfaa90d8636e12c329a68f714e1d9fd854361f --- /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 0000000000000000000000000000000000000000..9d0c84edb36640ac3de5710fc1dbe79e6411a004 --- /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 0000000000000000000000000000000000000000..1b4c017230274d01c3a95e155adad30f4bb0d51e --- /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 0000000000000000000000000000000000000000..8d74fd1d56be9dc45946297884e6b50595ec8692 --- /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 76a5accc5fe9554fff114cd4b3be2fd40e9d2603..1d9e9334f043faea35e60ff80b4ab85cebcd473e 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 0000000000000000000000000000000000000000..dbbd69bdd3b575326f724311a7a49e1f3c31c1eb --- /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 0000000000000000000000000000000000000000..fb5502a1795d2d04af2bd03cc08d9b5b52e03eed --- /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 0000000000000000000000000000000000000000..eb8cc9c63e7535193decc1c7077198f99ec652cb --- /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 be9f52e00b169224977d25962ca8947a69ef9eb7..7a293e80c1833da6a7430a371dcab8b367ad7db4 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 0000000000000000000000000000000000000000..9a40e8a7201a9439978523ea5e68a35d4daf06ed --- /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 8a11e31014d55ecfe82338baa2805db3ffdeba93..79a528c59f497bd04b1dea4a09a5d615ee1f71a1 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 78d3a9dd9f9ee323358ff1f8504f3c81fd913b59..a467c84845cd2846bb18f6efc6e275466e49bea0 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 ef253801b118e82d70f566a051b1b03ae26018d1..c7eb1db3e74bb9c9f46687c104329a9d4e44d10e 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 0000000000000000000000000000000000000000..840961938cb261d114eb509b9c3ce6d1d76d9c0c --- /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 580644347c846a544c820868b1ace604c6f93694..c9482ceb00f54b853f84aee43faa883d89efacc2 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 0000000000000000000000000000000000000000..723f916f99c85ed18a2bb3994998a37abf065a1a --- /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 0000000000000000000000000000000000000000..64f07e20e9585baf5430ad1359849d50a64bb766 --- /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 0000000000000000000000000000000000000000..a4746dea7ac571e00e9c74f68ecc61b91baea051 --- /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 0000000000000000000000000000000000000000..f19b2f7da8a50311524681574b0cabc0c1eafd0e --- /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 15d7601dde34c8f1c6843b39e2650f8917b1b6d1..410260074fbd5f157b8f366bc540f7ad35593092 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 0000000000000000000000000000000000000000..b78ba82bd8feb834c87cd5fd788371fa3784f076 --- /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/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java index 67b70684eede707d6df32b60d3a1b455cca71935..12d6161eb7185e50ad9e88486ac67f485f1a2b8e 100644 --- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java @@ -16,6 +16,8 @@ package com.android.server.contentcapture; +import static android.view.contentprotection.flags.Flags.FLAG_PARSE_GROUPS_CONFIG_ENABLED; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; @@ -33,6 +35,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; +import android.platform.test.flag.junit.SetFlagsRule; import android.service.contentcapture.ContentCaptureServiceInfo; import android.view.contentcapture.ContentCaptureEvent; @@ -56,6 +59,8 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.List; + /** * Test for {@link ContentCaptureManagerService}. * @@ -84,6 +89,8 @@ public class ContentCaptureManagerServiceTest { @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock private UserManagerInternal mMockUserManagerInternal; @Mock private ContentProtectionBlocklistManager mMockContentProtectionBlocklistManager; @@ -437,23 +444,81 @@ public class ContentCaptureManagerServiceTest { } @Test - public void parseContentProtectionGroupsConfig_null() { + public void parseContentProtectionGroupsConfig_disabled_null() { + mSetFlagsRule.disableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED); ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + assertThat(service.parseContentProtectionGroupsConfig(null)).isEmpty(); } @Test - public void parseContentProtectionGroupsConfig_empty() { + public void parseContentProtectionGroupsConfig_disabled_empty() { + mSetFlagsRule.disableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED); ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + assertThat(service.parseContentProtectionGroupsConfig("")).isEmpty(); } @Test - public void parseContentProtectionGroupsConfig_notEmpty() { + public void parseContentProtectionGroupsConfig_disabled_notEmpty() { + mSetFlagsRule.disableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED); ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + assertThat(service.parseContentProtectionGroupsConfig("a")).isEmpty(); } + @Test + public void parseContentProtectionGroupsConfig_enabled_null() { + mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED); + ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + + assertThat(service.parseContentProtectionGroupsConfig(null)).isEmpty(); + } + + @Test + public void parseContentProtectionGroupsConfig_enabled_empty() { + mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED); + ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + + assertThat(service.parseContentProtectionGroupsConfig("")).isEmpty(); + } + + @Test + public void parseContentProtectionGroupsConfig_enabled_singleValue() { + mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED); + ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + + assertThat(service.parseContentProtectionGroupsConfig("a")) + .isEqualTo(List.of(List.of("a"))); + } + + @Test + public void parseContentProtectionGroupsConfig_enabled_multipleValues() { + mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED); + ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + + assertThat(service.parseContentProtectionGroupsConfig("a,b")) + .isEqualTo(List.of(List.of("a", "b"))); + } + + @Test + public void parseContentProtectionGroupsConfig_enabled_multipleGroups() { + mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED); + ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + + assertThat(service.parseContentProtectionGroupsConfig("a,b;c;d,e")) + .isEqualTo(List.of(List.of("a", "b"), List.of("c"), List.of("d", "e"))); + } + + @Test + public void parseContentProtectionGroupsConfig_enabled_emptyValues() { + mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED); + ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + + assertThat(service.parseContentProtectionGroupsConfig("a;b;;;c;,d,,e,;,;")) + .isEqualTo(List.of(List.of("a"), List.of("b"), List.of("c"), List.of("d", "e"))); + } + private class TestContentCaptureManagerService extends ContentCaptureManagerService { TestContentCaptureManagerService() { diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java index 9b32a809d2b50f0956e550e0c8a65df266dc9745..de3cfbf859ffbf4e0932bae8bcd8fdd1d61af889 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java @@ -54,8 +54,8 @@ import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockSettingsInternal; import com.android.server.AlarmManagerInternal; import com.android.server.LocalServices; -import com.android.server.PersistentDataBlockManagerInternal; import com.android.server.net.NetworkPolicyManagerInternal; +import com.android.server.pdb.PersistentDataBlockManagerInternal; import com.android.server.pm.PackageManagerLocal; import com.android.server.pm.UserManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java index 16fdfb16444a03a55de3d24cf952b7a2b84bf3a0..76aa40c04e3dd5203e4120b0238f17b1a909ee50 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java @@ -77,7 +77,7 @@ import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockSettingsInternal; import com.android.server.AlarmManagerInternal; -import com.android.server.PersistentDataBlockManagerInternal; +import com.android.server.pdb.PersistentDataBlockManagerInternal; import com.android.server.net.NetworkPolicyManagerInternal; import com.android.server.pm.PackageManagerLocal; import com.android.server.pm.UserManagerInternal; diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java index a029db92237285e9b57c1939819cde671562d4be..fa3c7a4c47692e67a927faa2d0003659f892b723 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java @@ -22,7 +22,7 @@ import static org.mockito.Mockito.mock; import android.content.Context; -import com.android.server.PersistentDataBlockManagerInternal; +import com.android.server.pdb.PersistentDataBlockManagerInternal; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java index 23f14f8468bbcc944aa6d1e6e46e60708f8679c8..02b86db6ab6fd95f98283f4e9b962a4a22feeebe 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java @@ -53,8 +53,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.util.test.FakeSettingsProviderRule; -import com.android.server.PersistentDataBlockManagerInternal; import com.android.server.locksettings.LockSettingsStorage.PersistentData; +import com.android.server.pdb.PersistentDataBlockManagerInternal; import org.junit.After; import org.junit.Before; diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java index d85768dd758891776c2ab753adbfe57dfe9d3205..f94aff706a67015e9225588d6587d78813a85f6e 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java @@ -26,6 +26,7 @@ import static android.media.projection.ReviewGrantedConsentResult.UNKNOWN; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -66,6 +67,7 @@ import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; import com.android.server.testutils.OffsettableClock; import com.android.server.wm.WindowManagerInternal; @@ -128,6 +130,14 @@ public class MediaProjectionManagerServiceTest { } }; + private final MediaProjectionManagerService.Injector mMediaProjectionMetricsLoggerInjector = + new MediaProjectionManagerService.Injector() { + @Override + MediaProjectionMetricsLogger mediaProjectionMetricsLogger() { + return mMediaProjectionMetricsLogger; + } + }; + private Context mContext; private MediaProjectionManagerService mService; private OffsettableClock mClock; @@ -142,6 +152,8 @@ public class MediaProjectionManagerServiceTest { private PackageManager mPackageManager; @Mock private IMediaProjectionWatcherCallback mWatcherCallback; + @Mock + private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; @Captor private ArgumentCaptor<ContentRecordingSession> mSessionCaptor; @@ -733,6 +745,25 @@ public class MediaProjectionManagerServiceTest { ); } + @Test + public void setContentRecordingSession_success_logsCaptureInProgress() + throws Exception { + mService.addCallback(mWatcherCallback); + MediaProjectionManagerService service = new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); + MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + projection.start(mIMediaProjectionCallback); + doReturn(true).when(mWindowManagerInternal).setContentRecordingSession( + any(ContentRecordingSession.class)); + + service.setContentRecordingSession(DISPLAY_SESSION); + + verify(mMediaProjectionMetricsLogger).notifyProjectionStateChange( + projection.uid, + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS, + FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN + ); + } + @Test public void setContentRecordingSession_notifiesListenersOnCallbackLooper() throws Exception { diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp index ca5cfa5b60f5fcc1ec1e128f469fa8077f6c9b17..95441060f0e5e5445c74451748f3943dd9dcb86c 100644 --- a/services/tests/vibrator/Android.bp +++ b/services/tests/vibrator/Android.bp @@ -27,6 +27,7 @@ android_test { "androidx.test.runner", "androidx.test.rules", "androidx.test.ext.junit", + "flag-junit", "frameworks-base-testutils", "frameworks-services-vibrator-testutils", "junit", diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java index bc826a3cf4a6dcd9ae8b8b550bd6480ed14b4423..04158c4d4f937e9707df52df214f73d1ee6fccea 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java @@ -31,6 +31,8 @@ import static org.mockito.Mockito.when; import android.content.res.Resources; import android.os.VibrationEffect; import android.os.VibratorInfo; +import android.os.vibrator.Flags; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.AtomicFile; import android.util.SparseArray; @@ -49,6 +51,8 @@ import java.io.File; import java.io.FileOutputStream; public class HapticFeedbackCustomizationTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Rule public MockitoRule rule = MockitoJUnit.rule(); // Pairs of valid vibration XML along with their equivalent VibrationEffect. @@ -77,6 +81,7 @@ public class HapticFeedbackCustomizationTest { @Before public void setUp() { when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(true); + mSetFlagsRule.enableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED); } @Test @@ -86,6 +91,21 @@ public class HapticFeedbackCustomizationTest { /* expectedCustomizations= */ new SparseArray<>()); } + @Test + public void testParseCustomizations_featureFlagDisabled_returnsNull() throws Exception { + mSetFlagsRule.disableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED); + // Valid customization XML. + String xml = "<haptic-feedback-constants>" + + "<constant id=\"10\">" + + COMPOSITION_VIBRATION_XML + + "</constant>" + + "</haptic-feedback-constants>"; + setupCustomizationFile(xml); + + assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock)) + .isNull(); + } + @Test public void testParseCustomizations_oneVibrationCustomization_success() throws Exception { String xml = "<haptic-feedback-constants>" diff --git a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java index 6c48a6961bc29da4b4ac9872b9eea46b9bf497b4..9f43a1785266e69694355111724145fcf814c0b5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java @@ -93,6 +93,16 @@ public class SafeActivityOptionsTest { assertSame(clone.getOriginalOptions().getLaunchRootTask(), token); } + @Test + public void test_selectiveCloneLunchRemoteTransition() { + final RemoteTransition transition = mock(RemoteTransition.class); + final SafeActivityOptions clone = new SafeActivityOptions( + ActivityOptions.makeRemoteTransition(transition)) + .selectiveCloneLaunchOptions(); + + assertSame(clone.getOriginalOptions().getRemoteTransition(), transition); + } + @Test public void test_getOptions() { // Mock everything necessary diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index 5d2f27dce2067b06e60c72dac3c1c7bf415647a1..35e2fcfecb6bc870714e86446bcf3a1eef38551f 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -37,8 +37,8 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; -import android.hardware.usb.IUsbManager; import android.hardware.usb.IDisplayPortAltModeInfoListener; +import android.hardware.usb.IUsbManager; import android.hardware.usb.IUsbOperationInternal; import android.hardware.usb.ParcelableUsbPort; import android.hardware.usb.UsbAccessory; @@ -46,7 +46,6 @@ import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; import android.hardware.usb.UsbPort; import android.hardware.usb.UsbPortStatus; -import android.hardware.usb.DisplayPortAltModeInfo; import android.os.Binder; import android.os.Bundle; import android.os.ParcelFileDescriptor; @@ -1215,6 +1214,20 @@ public class UsbService extends IUsbManager.Stub { mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")), "", 0); } + } else if ("enable-usb-data".equals(args[0]) && args.length == 3) { + final String portId = args[1]; + final boolean enable = Boolean.parseBoolean(args[2]); + + if (mPortManager != null) { + for (UsbPort p : mPortManager.getPorts()) { + if (p.getId().equals(portId)) { + int res = p.enableUsbData(enable); + Slog.i(TAG, "enableUsbData " + portId + " status " + res); + break; + } + } + } + } else if ("ports".equals(args[0]) && args.length == 1) { if (mPortManager != null) { mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")), @@ -1293,6 +1306,11 @@ public class UsbService extends IUsbManager.Stub { pw.println("reset-displayport-status can also be used in order to set"); pw.println("the DisplayPortInfo to default values."); pw.println(); + pw.println("Example enableUsbData"); + pw.println("This dumpsys command functions for both simulated and real ports."); + pw.println(" dumpsys usb enable-usb-data \"matrix\" true"); + pw.println(" dumpsys usb enable-usb-data \"matrix\" false"); + pw.println(); pw.println("Example USB device descriptors:"); pw.println(" dumpsys usb dump-descriptors -dump-short"); pw.println(" dumpsys usb dump-descriptors -dump-tree"); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 63e91ad414deeb60acbf95df343a1424c8fe99ce..7a0bf9038f7c5e655ae94473c5c9774137a17ab3 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -10522,6 +10522,8 @@ public class CarrierConfigManager { "NR_NSA_MMWAVE", new int[]{8000, 10227, 12488, 15017, 15278}); auto_data_switch_rat_signal_score_string_bundle.putIntArray( "LTE", new int[]{3731, 5965, 8618, 11179, 13384}); + auto_data_switch_rat_signal_score_string_bundle.putIntArray( + "LTE_CA", new int[]{3831, 6065, 8718, 11379, 13484}); auto_data_switch_rat_signal_score_string_bundle.putIntArray( "NR_SA", new int[]{5288, 6795, 6955, 7562, 9713}); auto_data_switch_rat_signal_score_string_bundle.putIntArray( 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 79348d104745f263c362531df7f088773516f30c..ae7c2a99b8089aea495203aed6de6a8b778f244b 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; diff --git a/tests/FlickerTests/AndroidTestTemplate.xml b/tests/FlickerTests/AndroidTestTemplate.xml index 63acddf25e02672bac4b1f72ffee3001eae5d251..0f4798090da8b56ffc2225000204b4901e879746 100644 --- a/tests/FlickerTests/AndroidTestTemplate.xml +++ b/tests/FlickerTests/AndroidTestTemplate.xml @@ -61,9 +61,7 @@ <option name="shell-timeout" value="6600s"/> <option name="test-timeout" value="6600s"/> <option name="hidden-api-checks" value="false"/> - <!-- TODO(b/288396763): re-enable when PerfettoListener is fixed <option name="device-listeners" value="android.device.collectors.PerfettoListener"/> - --> <!-- PerfettoListener related arguments --> <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/> <option name="instrumentation-arg" diff --git a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt index 56ab755af47bb3d4b334cf821987d96f4775bffa..7e43566d56f8a123d09fc03ebee75abe1308a4bc 100644 --- a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt +++ b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt @@ -37,6 +37,8 @@ class ColorModeControls : LinearLayout, WindowObserver { private var window: Window? = null private var currentModeDisplay: TextView? = null + private var desiredRatio = 0.0f + override fun onFinishInflate() { super.onFinishInflate() val window = window ?: throw IllegalStateException("Failed to attach window") @@ -67,6 +69,7 @@ class ColorModeControls : LinearLayout, WindowObserver { override fun onAttachedToWindow() { super.onAttachedToWindow() + desiredRatio = window?.desiredHdrHeadroom ?: 0.0f val hdrVis = if (display.isHdrSdrRatioAvailable) { display.registerHdrSdrRatioChangedListener({ it.run() }, hdrSdrListener) View.VISIBLE @@ -83,6 +86,11 @@ class ColorModeControls : LinearLayout, WindowObserver { } private fun setColorMode(newMode: Int) { + if (newMode == ActivityInfo.COLOR_MODE_HDR && + window!!.colorMode == ActivityInfo.COLOR_MODE_HDR) { + desiredRatio = (desiredRatio + 1) % 5.0f + window!!.desiredHdrHeadroom = desiredRatio + } window!!.colorMode = newMode updateModeInfoDisplay() }